1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2026-01-02 10:43:31 +00:00

Compare commits

..

No commits in common. "2e600e76b03fab8bf49aa42a492c4ba207727575" and "f0e9581fe1070c38e1c4d803db60ca1e8f8ada62" have entirely different histories.

247 changed files with 6681 additions and 775 deletions

View File

@ -16,7 +16,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v2
with:
dotnet-version: "8.0.x"
dotnet-version: "7.0.x"
- name: Setup DocFX
run: dotnet tool update -g docfx
- name: Build Core

View File

@ -35,7 +35,7 @@ jobs:
matrix:
include:
- os: windows-latest
rid: win-x64
rid: win10-x64
csproj: Windows
- os: ubuntu-latest
@ -60,7 +60,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v2
with:
dotnet-version: '8.0.x'
dotnet-version: '7.0.x'
- name: Publish Artemis
run: dotnet publish --configuration Release -p:Version=${{ needs.version.outputs.version-number }} --runtime ${{ matrix.rid }} --output build/${{ matrix.rid }} --self-contained Artemis/src/Artemis.UI.${{ matrix.csproj }}/Artemis.UI.${{ matrix.csproj }}.csproj
- name: Publish Plugins

View File

@ -37,7 +37,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v2
with:
dotnet-version: '8.0.x'
dotnet-version: '7.0.x'
- name: Checkout
uses: actions/checkout@v3
- name: Pack Artemis.Core

View File

@ -36,20 +36,20 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="DryIoc.dll" />
<PackageReference Include="EmbedIO" />
<PackageReference Include="HidSharp" />
<PackageReference Include="Humanizer.Core" />
<PackageReference Include="JetBrains.Annotations" />
<PackageReference Include="McMaster.NETCore.Plugins" />
<PackageReference Include="Newtonsoft.Json" />
<PackageReference Include="RGB.NET.Core" />
<PackageReference Include="RGB.NET.Layout" />
<PackageReference Include="RGB.NET.Presets" />
<PackageReference Include="Serilog.Sinks.Console" />
<PackageReference Include="Serilog.Sinks.Debug" />
<PackageReference Include="Serilog.Sinks.File" />
<PackageReference Include="SkiaSharp" />
<PackageReference Include="DryIoc.dll" Version="5.4.3" />
<PackageReference Include="EmbedIO" Version="3.5.2" />
<PackageReference Include="HidSharp" Version="2.1.0" />
<PackageReference Include="Humanizer.Core" Version="2.14.1" />
<PackageReference Include="JetBrains.Annotations" Version="2023.3.0" />
<PackageReference Include="McMaster.NETCore.Plugins" Version="1.4.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="RGB.NET.Core" Version="2.0.4-prerelease.16" />
<PackageReference Include="RGB.NET.Layout" Version="2.0.4-prerelease.16" />
<PackageReference Include="RGB.NET.Presets" Version="2.0.4-prerelease.16" />
<PackageReference Include="Serilog.Sinks.Console" Version="5.0.1" />
<PackageReference Include="Serilog.Sinks.Debug" Version="2.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
<PackageReference Include="SkiaSharp" Version="2.88.7" />
</ItemGroup>
<ItemGroup>

View File

@ -5,7 +5,7 @@ using Artemis.Core.DryIoc.Factories;
using Artemis.Core.Providers;
using Artemis.Core.Services;
using Artemis.Storage;
using Artemis.Storage.Migrations;
using Artemis.Storage.Migrations.Interfaces;
using Artemis.Storage.Repositories.Interfaces;
using DryIoc;
@ -36,7 +36,6 @@ public static class ContainerExtensions
// Bind migrations
container.RegisterMany(storageAssembly, type => type.IsAssignableTo<IStorageMigration>(), Reuse.Singleton, nonPublicServiceTypes: true);
container.RegisterMany(storageAssembly, type => type.IsAssignableTo<IProfileMigration>(), Reuse.Singleton, nonPublicServiceTypes: true);
container.RegisterMany(coreAssembly, type => type.IsAssignableTo<ILayoutProvider>(), Reuse.Singleton);
container.Register<IPluginSettingsFactory, PluginSettingsFactory>(Reuse.Singleton);

View File

@ -248,8 +248,8 @@ public sealed class Layer : RenderProfileElement
typeof(PropertyGroupDescriptionAttribute)
)!;
LayerEntity.GeneralPropertyGroup ??= new PropertyGroupEntity {Identifier = generalAttribute.Identifier!};
LayerEntity.TransformPropertyGroup ??= new PropertyGroupEntity {Identifier = transformAttribute.Identifier!};
LayerEntity.GeneralPropertyGroup ??= new PropertyGroupEntity {Identifier = generalAttribute.Identifier};
LayerEntity.TransformPropertyGroup ??= new PropertyGroupEntity {Identifier = transformAttribute.Identifier};
General.Initialize(this, null, generalAttribute, LayerEntity.GeneralPropertyGroup);
Transform.Initialize(this, null, transformAttribute, LayerEntity.TransformPropertyGroup);

View File

@ -240,8 +240,7 @@ public abstract class LayerPropertyGroup : IDisposable
foreach (LayerPropertyGroup layerPropertyGroup in LayerPropertyGroups)
{
layerPropertyGroup.ApplyToEntity();
if (layerPropertyGroup.PropertyGroupEntity != null)
PropertyGroupEntity.PropertyGroups.Add(layerPropertyGroup.PropertyGroupEntity);
PropertyGroupEntity.PropertyGroups.Add(layerPropertyGroup.PropertyGroupEntity);
}
}

View File

@ -46,7 +46,7 @@ public class ArtemisDevice : CorePropertyChanged
InputIdentifiers = new List<ArtemisDeviceInputIdentifier>();
InputMappings = new Dictionary<ArtemisLed, ArtemisLed>();
Categories = new HashSet<DeviceCategory>();
LayoutSelection = new LayoutSelection {Type = DefaultLayoutProvider.LAYOUT_TYPE};
LayoutSelection = new LayoutSelection {Type = DefaultLayoutProvider.LayoutType};
RgbDevice.ColorCorrections.Clear();
RgbDevice.ColorCorrections.Add(new ScaleColorCorrection(this));
@ -75,7 +75,7 @@ public class ArtemisDevice : CorePropertyChanged
InputIdentifiers = new List<ArtemisDeviceInputIdentifier>();
InputMappings = new Dictionary<ArtemisLed, ArtemisLed>();
Categories = new HashSet<DeviceCategory>();
LayoutSelection = new LayoutSelection {Type = DefaultLayoutProvider.LAYOUT_TYPE};
LayoutSelection = new LayoutSelection {Type = DefaultLayoutProvider.LayoutType};
foreach (DeviceInputIdentifierEntity identifierEntity in DeviceEntity.InputIdentifiers)
InputIdentifiers.Add(new ArtemisDeviceInputIdentifier(identifierEntity.InputProvider, identifierEntity.Identifier));
@ -155,9 +155,6 @@ public class ArtemisDevice : CorePropertyChanged
/// </summary>
public HashSet<DeviceCategory> Categories { get; }
/// <summary>
/// Gets the layout selection applied to this device
/// </summary>
public LayoutSelection LayoutSelection { get; }
/// <summary>

View File

@ -57,9 +57,6 @@ public class ArtemisLayout
/// </summary>
public LayoutCustomDeviceData LayoutCustomDeviceData { get; private set; } = null!;
/// <summary>
/// Gets a boolean indicating whether this layout is a default layout or not
/// </summary>
public bool IsDefaultLayout { get; private set; }
/// <summary>

View File

@ -65,7 +65,7 @@ public class LayerBrushDescriptor
BaseLayerBrush brush = (BaseLayerBrush) Provider.Plugin.Resolve(LayerBrushType);
brush.Layer = layer;
brush.Descriptor = this;
brush.LayerBrushEntity = entity ?? new LayerBrushEntity {ProviderId = Provider.Id, BrushType = LayerBrushType.FullName ?? throw new InvalidOperationException()};
brush.LayerBrushEntity = entity ?? new LayerBrushEntity {ProviderId = Provider.Id, BrushType = LayerBrushType.FullName};
brush.Initialize();
return brush;

View File

@ -231,7 +231,7 @@ public abstract class BaseLayerEffect : BreakableModel, IDisposable, IStorageMod
return;
LayerEffectEntity.ProviderId = Descriptor.Provider.Id;
LayerEffectEntity.EffectType = GetType().FullName ?? throw new InvalidOperationException();
LayerEffectEntity.EffectType = GetType().FullName;
BaseProperties?.ApplyToEntity();
LayerEffectEntity.PropertyGroup = BaseProperties?.PropertyGroupEntity;
}

View File

@ -1,81 +0,0 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Reflection;
using SkiaSharp;
namespace Artemis.Core.Nodes;
/// <summary>
/// Allows you to register one or more <see cref="INode" />s usable by node scripts.
/// </summary>
public abstract class NodeProvider : PluginFeature
{
private readonly List<NodeData> _nodeDescriptors;
/// <summary>
/// Creates a new instance of the <see cref="NodeProvider"/> class.
/// </summary>
public NodeProvider()
{
_nodeDescriptors = new List<NodeData>();
NodeDescriptors = new ReadOnlyCollection<NodeData>(_nodeDescriptors);
Disabled += OnDisabled;
}
/// <summary>
/// A read-only collection of all nodes added with <see cref="RegisterNodeType{T}" />
/// </summary>
public ReadOnlyCollection<NodeData> NodeDescriptors { get; set; }
/// <summary>
/// Adds a node descriptor for a given node, so that it appears in the UI.
/// <para>Note: You do not need to manually remove these on disable</para>
/// </summary>
/// <typeparam name="T">The type of the node you wish to register</typeparam>
protected void RegisterNodeType<T>() where T : INode
{
RegisterNodeType(typeof(T));
}
/// <summary>
/// Adds a node descriptor for a given node, so that it appears in the UI.
/// <para>Note: You do not need to manually remove these on disable</para>
/// </summary>
/// <param name="nodeType">The type of the node you wish to register</param>
protected void RegisterNodeType(Type nodeType)
{
if (!IsEnabled)
throw new ArtemisPluginFeatureException(this, "Can only add a node descriptor when the plugin is enabled");
if (nodeType == null)
throw new ArgumentNullException(nameof(nodeType));
if (!nodeType.IsAssignableTo(typeof(INode)))
throw new ArgumentException("Node has to be a base type of the Node-Type.", nameof(nodeType));
NodeAttribute? nodeAttribute = nodeType.GetCustomAttribute<NodeAttribute>();
string name = nodeAttribute?.Name ?? nodeType.Name;
string description = nodeAttribute?.Description ?? string.Empty;
string category = nodeAttribute?.Category ?? string.Empty;
string helpUrl = nodeAttribute?.HelpUrl ?? string.Empty;
NodeData nodeData = new(this, nodeType, name, description, category, helpUrl, nodeAttribute?.InputType, nodeAttribute?.OutputType);
_nodeDescriptors.Add(nodeData);
NodeTypeStore.Add(nodeData);
}
/// <summary>
/// Adds a color for lines of the provided type.
/// </summary>
/// <param name="color">The color to add.</param>
/// <typeparam name="T">The type to use the color for.</typeparam>
protected TypeColorRegistration RegisterTypeColor<T>(SKColor color)
{
return NodeTypeStore.AddColor(typeof(T), color, this);
}
private void OnDisabled(object? sender, EventArgs e)
{
// The store will clean up the registrations by itself, the plugin feature just needs to clear its own list
_nodeDescriptors.Clear();
}
}

View File

@ -41,7 +41,7 @@ public class PluginSettings
if (_settingEntities.ContainsKey(name))
return (PluginSetting<T>) _settingEntities[name];
// Try to find in database
PluginSettingEntity? settingEntity = _pluginRepository.GetSettingByNameAndGuid(name, Plugin.Guid);
PluginSettingEntity settingEntity = _pluginRepository.GetSettingByNameAndGuid(name, Plugin.Guid);
// If not found, create a new one
if (settingEntity == null)
{

View File

@ -1,14 +1,8 @@
namespace Artemis.Core.Providers;
/// <summary>
/// Represents a layout provider that loads a layout from a custom path.
/// </summary>
public class CustomPathLayoutProvider : ILayoutProvider
{
/// <summary>
/// The layout type of this layout provider.
/// </summary>
public const string LAYOUT_TYPE = "CustomPath";
public static string LayoutType = "CustomPath";
/// <inheritdoc />
public ArtemisLayout? GetDeviceLayout(ArtemisDevice device)
@ -27,7 +21,7 @@ public class CustomPathLayoutProvider : ILayoutProvider
/// <inheritdoc />
public bool IsMatch(ArtemisDevice device)
{
return device.LayoutSelection.Type == LAYOUT_TYPE;
return device.LayoutSelection.Type == LayoutType;
}
/// <summary>
@ -37,7 +31,7 @@ public class CustomPathLayoutProvider : ILayoutProvider
/// <param name="path">The path to the custom layout.</param>
public void ConfigureDevice(ArtemisDevice device, string? path)
{
device.LayoutSelection.Type = LAYOUT_TYPE;
device.LayoutSelection.Type = LayoutType;
device.LayoutSelection.Parameter = path;
}
}

View File

@ -1,14 +1,8 @@
namespace Artemis.Core.Providers;
/// <summary>
/// Represents a layout provider that loads a layout from the plugin and falls back to a default layout.
/// </summary>
public class DefaultLayoutProvider : ILayoutProvider
{
/// <summary>
/// The layout type of this layout provider.
/// </summary>
public const string LAYOUT_TYPE = "Default";
public static string LayoutType = "Default";
/// <inheritdoc />
public ArtemisLayout? GetDeviceLayout(ArtemisDevice device)
@ -32,7 +26,7 @@ public class DefaultLayoutProvider : ILayoutProvider
/// <inheritdoc />
public bool IsMatch(ArtemisDevice device)
{
return device.LayoutSelection.Type == LAYOUT_TYPE;
return device.LayoutSelection.Type == LayoutType;
}
/// <summary>
@ -41,7 +35,7 @@ public class DefaultLayoutProvider : ILayoutProvider
/// <param name="device">The device to apply the provider to.</param>
public void ConfigureDevice(ArtemisDevice device)
{
device.LayoutSelection.Type = LAYOUT_TYPE;
device.LayoutSelection.Type = LayoutType;
device.LayoutSelection.Parameter = null;
}
}

View File

@ -12,17 +12,6 @@ public interface ILayoutProvider
/// <returns>The resulting layout if one was available; otherwise <see langword="null" />.</returns>
ArtemisLayout? GetDeviceLayout(ArtemisDevice device);
/// <summary>
/// Applies the layout to the provided device.
/// </summary>
/// <param name="device">The device to apply to.</param>
/// <param name="layout">The layout to apply.</param>
void ApplyLayout(ArtemisDevice device, ArtemisLayout layout);
/// <summary>
/// Determines whether the provided device is configured to use this layout provider.
/// </summary>
/// <param name="device">The device to check.</param>
/// <returns>A value indicating whether the provided device is configured to use this layout provider.</returns>
bool IsMatch(ArtemisDevice device);
}

View File

@ -1,14 +1,8 @@
namespace Artemis.Core.Providers;
/// <summary>
/// Represents a layout provider that does not load a layout.
/// </summary>
public class NoneLayoutProvider : ILayoutProvider
{
/// <summary>
/// The layout type of this layout provider.
/// </summary>
public const string LAYOUT_TYPE = "None";
public static string LayoutType = "None";
/// <inheritdoc />
public ArtemisLayout? GetDeviceLayout(ArtemisDevice device)
@ -25,7 +19,7 @@ public class NoneLayoutProvider : ILayoutProvider
/// <inheritdoc />
public bool IsMatch(ArtemisDevice device)
{
return device.LayoutSelection.Type == LAYOUT_TYPE;
return device.LayoutSelection.Type == LayoutType;
}
/// <summary>
@ -34,7 +28,7 @@ public class NoneLayoutProvider : ILayoutProvider
/// <param name="device">The device to apply the provider to.</param>
public void ConfigureDevice(ArtemisDevice device)
{
device.LayoutSelection.Type = LAYOUT_TYPE;
device.LayoutSelection.Type = LayoutType;
device.LayoutSelection.Parameter = null;
}
}

View File

@ -1,9 +1,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using Artemis.Storage.Entities.Profile.Nodes;
using DryIoc;
using Newtonsoft.Json;
using SkiaSharp;
@ -11,8 +13,31 @@ namespace Artemis.Core.Services;
internal class NodeService : INodeService
{
#region Constants
private static readonly Type TypeNode = typeof(INode);
#endregion
private readonly IContainer _container;
#region Constructors
public NodeService(IContainer container)
{
_container = container;
}
#endregion
#region Properties & Fields
public IEnumerable<NodeData> AvailableNodes => NodeTypeStore.GetAll();
#endregion
#region Methods
/// <inheritdoc />
public List<Type> GetRegisteredTypes()
{
@ -28,7 +53,7 @@ internal class NodeService : INodeService
// Objects represent an input that can take any type, these are hardcoded white
if (type == typeof(object))
return new TypeColorRegistration(type, new SKColor(255, 255, 255, 255), Constants.CorePluginFeature);
return new TypeColorRegistration(type, new SKColor(255, 255, 255, 255), Constants.CorePlugin);
// Come up with a random color based on the type name that should be the same each time
MD5 md5Hasher = MD5.Create();
@ -36,7 +61,32 @@ internal class NodeService : INodeService
int hash = BitConverter.ToInt32(hashed, 0);
SKColor baseColor = SKColor.FromHsl(hash % 255, 50 + hash % 50, 50);
return new TypeColorRegistration(type, baseColor, Constants.CorePluginFeature);
return new TypeColorRegistration(type, baseColor, Constants.CorePlugin);
}
public NodeTypeRegistration RegisterNodeType(Plugin plugin, Type nodeType)
{
if (plugin == null) throw new ArgumentNullException(nameof(plugin));
if (nodeType == null) throw new ArgumentNullException(nameof(nodeType));
if (!TypeNode.IsAssignableFrom(nodeType)) throw new ArgumentException("Node has to be a base type of the Node-Type.", nameof(nodeType));
NodeAttribute? nodeAttribute = nodeType.GetCustomAttribute<NodeAttribute>();
string name = nodeAttribute?.Name ?? nodeType.Name;
string description = nodeAttribute?.Description ?? string.Empty;
string category = nodeAttribute?.Category ?? string.Empty;
string helpUrl = nodeAttribute?.HelpUrl ?? string.Empty;
NodeData nodeData = new(plugin, nodeType, name, description, category, helpUrl, nodeAttribute?.InputType, nodeAttribute?.OutputType, (s, e) => CreateNode(s, e, nodeType));
return NodeTypeStore.Add(nodeData);
}
public TypeColorRegistration RegisterTypeColor(Plugin plugin, Type type, SKColor color)
{
if (plugin == null) throw new ArgumentNullException(nameof(plugin));
if (type == null) throw new ArgumentNullException(nameof(type));
return NodeTypeStore.AddColor(type, color, plugin);
}
public string ExportScript(NodeScript nodeScript)
@ -53,6 +103,33 @@ internal class NodeService : INodeService
target.LoadFromEntity(entity);
}
private INode CreateNode(INodeScript script, NodeEntity? entity, Type nodeType)
{
INode node = _container.Resolve(nodeType) as INode ?? throw new InvalidOperationException($"Node {nodeType} is not an INode");
if (node is Node concreteNode)
concreteNode.Container = _container;
if (entity != null)
{
node.X = entity.X;
node.Y = entity.Y;
try
{
if (node is Node nodeImplementation)
nodeImplementation.DeserializeStorage(entity.Storage);
}
catch
{
// ignored
}
}
node.TryInitialize(script);
return node;
}
#endregion
}
/// <summary>
@ -76,6 +153,21 @@ public interface INodeService : IArtemisService
/// </summary>
TypeColorRegistration GetTypeColorRegistration(Type type);
/// <summary>
/// Registers a node of the provided <paramref name="nodeType" />
/// </summary>
/// <param name="plugin">The plugin the node belongs to</param>
/// <param name="nodeType">The type of node to initialize</param>
NodeTypeRegistration RegisterNodeType(Plugin plugin, Type nodeType);
/// <summary>
/// Registers a type with a provided color for use in the node editor
/// </summary>
/// <param name="plugin">The plugin making the registration</param>
/// <param name="type">The type to associate the color with</param>
/// <param name="color">The color to display</param>
TypeColorRegistration RegisterTypeColor(Plugin plugin, Type type, SKColor color);
/// <summary>
/// Exports the provided node script to JSON.
/// </summary>

View File

@ -7,9 +7,6 @@ using System.Threading;
namespace Artemis.Core.Services;
/// <summary>
/// Represents a monitor that efficiently keeps track of running processes.
/// </summary>
public static partial class ProcessMonitor
{
#region Properties & Fields
@ -18,11 +15,8 @@ public static partial class ProcessMonitor
private static Timer? _timer;
private static readonly Dictionary<int, ProcessInfo> _processes = new();
private static Dictionary<int, ProcessInfo> _processes = new();
/// <summary>
/// Gets an immutable array of the current processes.
/// </summary>
public static ImmutableArray<ProcessInfo> Processes
{
get
@ -31,17 +25,9 @@ public static partial class ProcessMonitor
return _processes.Values.ToImmutableArray();
}
}
/// <summary>
/// Gets the date time at which the last update took place.
/// </summary>
public static DateTime LastUpdate { get; private set; }
private static TimeSpan _updateInterval = TimeSpan.FromSeconds(1);
/// <summary>
/// Gets or sets the interval at which to update the list of processes.
/// </summary>
public static TimeSpan UpdateInterval
{
get => _updateInterval;
@ -54,9 +40,6 @@ public static partial class ProcessMonitor
}
}
/// <summary>
/// Gets a value indicating whether the monitoring has started.
/// </summary>
public static bool IsStarted
{
get
@ -70,14 +53,7 @@ public static partial class ProcessMonitor
#region Events
/// <summary>
/// Occurs when a new process is started.
/// </summary>
public static event EventHandler<ProcessEventArgs>? ProcessStarted;
/// <summary>
/// Occurs when a process is stopped.
/// </summary>
public static event EventHandler<ProcessEventArgs>? ProcessStopped;
#endregion
@ -93,9 +69,6 @@ public static partial class ProcessMonitor
#region Methods
/// <summary>
/// Starts monitoring processes.
/// </summary>
public static void Start()
{
lock (LOCK)
@ -114,9 +87,6 @@ public static partial class ProcessMonitor
}
}
/// <summary>
/// Stops monitoring processes.
/// </summary>
public static void Stop()
{
lock (LOCK)
@ -130,7 +100,7 @@ public static partial class ProcessMonitor
FreeBuffer();
}
}
/// <summary>
/// Returns whether the specified process is running
/// </summary>
@ -141,7 +111,7 @@ public static partial class ProcessMonitor
{
if (!IsStarted || (processName == null && processLocation == null))
return false;
lock (LOCK)
{
return _processes.Values.Any(x => IsProcessRunning(x, processName, processLocation));
@ -160,19 +130,19 @@ public static partial class ProcessMonitor
OnProcessStopped(info);
}
}
private static bool IsProcessRunning(ProcessInfo info, string? processName, string? processLocation)
{
if (processName != null && processLocation != null)
return string.Equals(info.ProcessName, processName, StringComparison.InvariantCultureIgnoreCase) &&
string.Equals(Path.GetDirectoryName(info.Executable), processLocation, StringComparison.InvariantCultureIgnoreCase);
if (processName != null)
return string.Equals(info.ProcessName, processName, StringComparison.InvariantCultureIgnoreCase);
if (processLocation != null)
return string.Equals(Path.GetDirectoryName(info.Executable), processLocation, StringComparison.InvariantCultureIgnoreCase);
return false;
}
@ -182,10 +152,7 @@ public static partial class ProcessMonitor
{
ProcessStarted?.Invoke(null, new ProcessEventArgs(processInfo));
}
catch
{
/* Subscribers are idiots! */
}
catch { /* Subscribers are idiots! */ }
}
private static void OnProcessStopped(ProcessInfo processInfo)
@ -194,10 +161,7 @@ public static partial class ProcessMonitor
{
ProcessStopped?.Invoke(null, new ProcessEventArgs(processInfo));
}
catch
{
/* Subscribers are idiots! */
}
catch { /* Subscribers are idiots! */ }
}
#endregion

View File

@ -42,7 +42,6 @@ internal class LayerBrushService : ILayerBrushService
BrushType = "SolidBrush"
});
defaultReference.Value ??= new LayerBrushReference();
defaultReference.Value.LayerBrushProviderId ??= "Artemis.Plugins.LayerBrushes.Color.ColorBrushProvider-92a9d6ba";
defaultReference.Value.BrushType ??= "SolidBrush";
return LayerBrushStore.Get(defaultReference.Value.LayerBrushProviderId, defaultReference.Value.BrushType)?.LayerBrushDescriptor;

View File

@ -8,10 +8,8 @@ using System.Text;
using System.Threading.Tasks;
using Artemis.Core.Modules;
using Artemis.Storage.Entities.Profile;
using Artemis.Storage.Migrations;
using Artemis.Storage.Repositories.Interfaces;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Serilog;
using SkiaSharp;
@ -26,10 +24,9 @@ internal class ProfileService : IProfileService
private readonly List<ArtemisKeyboardKeyEventArgs> _pendingKeyboardEvents = new();
private readonly List<ProfileCategory> _profileCategories;
private readonly IProfileRepository _profileRepository;
private readonly List<IProfileMigration> _profileMigrators;
private readonly List<Exception> _renderExceptions = new();
private readonly List<Exception> _updateExceptions = new();
private DateTime _lastRenderExceptionLog;
private DateTime _lastUpdateExceptionLog;
@ -38,15 +35,13 @@ internal class ProfileService : IProfileService
IPluginManagementService pluginManagementService,
IInputService inputService,
IDeviceService deviceService,
IProfileRepository profileRepository,
List<IProfileMigration> profileMigrators)
IProfileRepository profileRepository)
{
_logger = logger;
_profileCategoryRepository = profileCategoryRepository;
_pluginManagementService = pluginManagementService;
_deviceService = deviceService;
_profileRepository = profileRepository;
_profileMigrators = profileMigrators;
_profileCategories = new List<ProfileCategory>(_profileCategoryRepository.GetAll().Select(c => new ProfileCategory(c)).OrderBy(c => c.Order));
_deviceService.LedsChanged += DeviceServiceOnLedsChanged;
@ -63,7 +58,7 @@ internal class ProfileService : IProfileService
public ProfileConfiguration? FocusProfile { get; set; }
public ProfileElement? FocusProfileElement { get; set; }
public bool UpdateFocusProfile { get; set; }
public bool ProfileRenderingDisabled { get; set; }
/// <inheritdoc />
@ -226,7 +221,7 @@ internal class ProfileService : IProfileService
return profileConfiguration.Profile;
}
ProfileEntity? profileEntity;
ProfileEntity profileEntity;
try
{
profileEntity = _profileRepository.Get(profileConfiguration.Entity.ProfileId);
@ -285,7 +280,7 @@ internal class ProfileService : IProfileService
{
DeactivateProfile(profileConfiguration);
ProfileEntity? profileEntity = _profileRepository.Get(profileConfiguration.Entity.ProfileId);
ProfileEntity profileEntity = _profileRepository.Get(profileConfiguration.Entity.ProfileId);
if (profileEntity == null)
return;
@ -358,7 +353,7 @@ internal class ProfileService : IProfileService
DeactivateProfile(profileConfiguration);
SaveProfileCategory(profileConfiguration.Category);
ProfileEntity? profileEntity = _profileRepository.Get(profileConfiguration.Entity.ProfileId);
ProfileEntity profileEntity = _profileRepository.Get(profileConfiguration.Entity.ProfileId);
if (profileEntity != null)
_profileRepository.Remove(profileEntity);
@ -466,12 +461,7 @@ internal class ProfileService : IProfileService
await using Stream profileStream = profileEntry.Open();
using StreamReader profileReader = new(profileStream);
JObject? profileJson = JsonConvert.DeserializeObject<JObject>(await profileReader.ReadToEndAsync(), IProfileService.ExportSettings);
// Before deserializing, apply any pending migrations
MigrateProfile(configurationEntity, profileJson);
ProfileEntity? profileEntity = profileJson?.ToObject<ProfileEntity>(JsonSerializer.Create(IProfileService.ExportSettings));
ProfileEntity? profileEntity = JsonConvert.DeserializeObject<ProfileEntity>(await profileReader.ReadToEndAsync(), IProfileService.ExportSettings);
if (profileEntity == null)
throw new ArtemisCoreException("Could not import profile, failed to deserialize profile.json");
@ -524,10 +514,10 @@ internal class ProfileService : IProfileService
public async Task<ProfileConfiguration> OverwriteProfile(MemoryStream archiveStream, ProfileConfiguration profileConfiguration)
{
ProfileConfiguration imported = await ImportProfile(archiveStream, profileConfiguration.Category, true, true, null, profileConfiguration.Order + 1);
DeleteProfile(profileConfiguration);
SaveProfileCategory(imported.Category);
return imported;
}
@ -555,21 +545,6 @@ internal class ProfileService : IProfileService
}
}
private void MigrateProfile(ProfileConfigurationEntity configurationEntity, JObject? profileJson)
{
if (profileJson == null)
return;
foreach (IProfileMigration profileMigrator in _profileMigrators.OrderBy(m => m.Version))
{
if (profileMigrator.Version <= configurationEntity.Version)
continue;
profileMigrator.Migrate(profileJson);
configurationEntity.Version = profileMigrator.Version;
}
}
/// <summary>
/// Populates all missing LEDs on all currently active profiles
/// </summary>

View File

@ -100,7 +100,7 @@ public interface IWebServerService : IArtemisService
/// <summary>
/// Removes an existing Web API controller and restarts the web server
/// </summary>
/// <param name="registration">The registration of the controller to remove.</param>
/// <typeparam name="T">The type of Web API controller to remove</typeparam>
void RemoveController(WebApiControllerRegistration registration);
/// <summary>

View File

@ -13,13 +13,16 @@ internal class NodeTypeStore
public static NodeTypeRegistration Add(NodeData nodeData)
{
if (nodeData.Plugin == null)
throw new ArtemisCoreException("Cannot add a data binding modifier type that is not associated with a plugin");
NodeTypeRegistration typeRegistration;
lock (Registrations)
{
if (Registrations.Any(r => r.NodeData == nodeData))
throw new ArtemisCoreException($"Data binding modifier type store already contains modifier '{nodeData.Name}'");
typeRegistration = new NodeTypeRegistration(nodeData, nodeData.Provider) {IsInStore = true};
typeRegistration = new NodeTypeRegistration(nodeData, nodeData.Plugin) {IsInStore = true};
Registrations.Add(typeRegistration);
}
@ -57,12 +60,24 @@ internal class NodeTypeStore
}
}
public static TypeColorRegistration AddColor(Type type, SKColor color, PluginFeature pluginFeature)
public static Plugin? GetPlugin(INode node)
{
Type nodeType = node.GetType();
lock (Registrations)
{
return Registrations.FirstOrDefault(r => r.NodeData.Type == nodeType)?.Plugin;
}
}
public static TypeColorRegistration AddColor(Type type, SKColor color, Plugin plugin)
{
TypeColorRegistration typeColorRegistration;
lock (ColorRegistrations)
{
typeColorRegistration = new TypeColorRegistration(type, color, pluginFeature) {IsInStore = true};
if (ColorRegistrations.Any(r => r.Type == type))
throw new ArtemisCoreException($"Node color store already contains a color for '{type.Name}'");
typeColorRegistration = new TypeColorRegistration(type, color, plugin) {IsInStore = true};
ColorRegistrations.Add(typeColorRegistration);
}

View File

@ -9,12 +9,12 @@ namespace Artemis.Core;
/// </summary>
public class NodeTypeRegistration
{
internal NodeTypeRegistration(NodeData nodeData, PluginFeature pluginFeature)
internal NodeTypeRegistration(NodeData nodeData, Plugin plugin)
{
NodeData = nodeData;
PluginFeature = pluginFeature;
Plugin = plugin;
PluginFeature.Disabled += OnDisabled;
Plugin.Disabled += OnDisabled;
}
/// <summary>
@ -23,9 +23,9 @@ public class NodeTypeRegistration
public NodeData NodeData { get; }
/// <summary>
/// Gets the plugin feature the node is associated with
/// Gets the plugin the node is associated with
/// </summary>
public PluginFeature PluginFeature { get; }
public Plugin Plugin { get; }
/// <summary>
/// Gets a boolean indicating whether the registration is in the internal Core store
@ -39,12 +39,12 @@ public class NodeTypeRegistration
/// <returns><see langword="true" /> if the entity matches this registration; otherwise <see langword="false" />.</returns>
public bool MatchesEntity(NodeEntity entity)
{
return PluginFeature.Id == entity.ProviderId && NodeData.Type.Name == entity.Type;
return Plugin.Guid == entity.PluginId && NodeData.Type.Name == entity.Type;
}
private void OnDisabled(object? sender, EventArgs e)
{
PluginFeature.Disabled -= OnDisabled;
Plugin.Disabled -= OnDisabled;
if (IsInStore)
NodeTypeStore.Remove(this);
}
@ -55,13 +55,13 @@ public class NodeTypeRegistration
/// </summary>
public class TypeColorRegistration
{
internal TypeColorRegistration(Type type, SKColor color, PluginFeature pluginFeature)
internal TypeColorRegistration(Type type, SKColor color, Plugin plugin)
{
Type = type;
Color = color;
PluginFeature = pluginFeature;
Plugin = plugin;
PluginFeature.Disabled += OnDisabled;
Plugin.Disabled += OnDisabled;
}
/// <summary>
@ -80,9 +80,9 @@ public class TypeColorRegistration
public SKColor DarkenedColor => Color.Darken(0.35f);
/// <summary>
/// Gets the plugin feature this type color is associated with
/// Gets the plugin type color is associated with
/// </summary>
public PluginFeature PluginFeature { get; }
public Plugin Plugin { get; }
/// <summary>
/// Gets a boolean indicating whether the registration is in the internal Core store
@ -91,7 +91,7 @@ public class TypeColorRegistration
private void OnDisabled(object? sender, EventArgs e)
{
PluginFeature.Disabled -= OnDisabled;
Plugin.Disabled -= OnDisabled;
if (IsInStore)
NodeTypeStore.RemoveColor(this);
}

View File

@ -14,11 +14,6 @@ public interface INode : INotifyPropertyChanged, IBreakableModel
/// Gets or sets the ID of the node.
/// </summary>
Guid Id { get; set; }
/// <summary>
/// Gets or sets the node data with information about this node
/// </summary>
NodeData? NodeData { get; set; }
/// <summary>
/// Gets the name of the node

View File

@ -1,5 +1,4 @@
using System;
using Artemis.Core.Nodes;
using Artemis.Storage.Entities.Profile.Nodes;
namespace Artemis.Core;
@ -11,9 +10,9 @@ public class NodeData
{
#region Constructors
internal NodeData(NodeProvider provider, Type type, string name, string description, string category, string helpUrl, Type? inputType, Type? outputType)
internal NodeData(Plugin plugin, Type type, string name, string description, string category, string helpUrl, Type? inputType, Type? outputType, Func<INodeScript, NodeEntity?, INode> create)
{
Provider = provider;
Plugin = plugin;
Type = type;
Name = name;
Description = description;
@ -21,6 +20,7 @@ public class NodeData
HelpUrl = helpUrl;
InputType = inputType;
OutputType = outputType;
_create = create;
}
#endregion
@ -35,31 +35,14 @@ public class NodeData
/// <returns>The returning node of type <see cref="Type" /></returns>
public INode CreateNode(INodeScript script, NodeEntity? entity)
{
INode node = (INode) Provider.Plugin.Resolve(Type);
node.NodeData = this;
INode node = _create(script, entity);
if (string.IsNullOrWhiteSpace(node.Name))
node.Name = Name;
if (string.IsNullOrWhiteSpace(node.Description))
node.Description = Description;
if (string.IsNullOrWhiteSpace(node.HelpUrl))
node.HelpUrl = HelpUrl;
if (entity != null)
{
node.X = entity.X;
node.Y = entity.Y;
try
{
if (node is Node nodeImplementation)
nodeImplementation.DeserializeStorage(entity.Storage);
}
catch
{
// ignored
}
}
node.TryInitialize(script);
return node;
}
@ -108,11 +91,11 @@ public class NodeData
}
#region Properties & Fields
/// <summary>
/// Gets the node provider that provided this node data
/// Gets the plugin that provided this node data
/// </summary>
public NodeProvider Provider { get; }
public Plugin Plugin { get; }
/// <summary>
/// Gets the type of <see cref="INode" /> this data represents
@ -149,5 +132,7 @@ public class NodeData
/// </summary>
public Type? OutputType { get; }
private readonly Func<INodeScript, NodeEntity?, INode> _create;
#endregion
}

View File

@ -161,7 +161,6 @@ public abstract class NodeScript : CorePropertyChanged, INodeScript
{
foreach (INode node in _nodes)
{
// ReSharper disable once SuspiciousTypeConversion.Global - Provided by plugins
if (node is IDisposable disposable)
disposable.Dispose();
}
@ -182,7 +181,6 @@ public abstract class NodeScript : CorePropertyChanged, INodeScript
foreach (INode removeNode in removeNodes)
{
RemoveNode(removeNode);
// ReSharper disable once SuspiciousTypeConversion.Global - Provided by plugins
if (removeNode is IDisposable disposable)
disposable.Dispose();
}
@ -314,7 +312,7 @@ public abstract class NodeScript : CorePropertyChanged, INodeScript
NodeEntity nodeEntity = new()
{
Id = node.Id,
ProviderId = node.NodeData?.Provider.Id ?? Constants.CorePluginFeature.Id,
PluginId = NodeTypeStore.GetPlugin(node)?.Guid ?? Constants.CorePlugin.Guid,
Type = node.GetType().Name,
X = node.X,
Y = node.Y,

View File

@ -41,9 +41,6 @@ public abstract class Node : BreakableModel, INode
set => SetAndNotify(ref _id, value);
}
/// <inheritdoc />
public NodeData? NodeData { get; set; }
private string _name;
/// <inheritdoc />
@ -107,6 +104,8 @@ public abstract class Node : BreakableModel, INode
/// <inheritdoc />
public override string BrokenDisplayName => Name;
internal IContainer Container { get; set; } = null!;
#endregion
#region Construtors

View File

@ -25,9 +25,7 @@ public abstract class Node<TStorage, TViewModel> : Node<TStorage>, ICustomViewMo
/// <param name="nodeScript"></param>
public virtual TViewModel GetViewModel(NodeScript nodeScript)
{
if (NodeData == null)
throw new ArtemisCoreException("Nodes without node data (default nodes or exit nodes) cannot have custom view models");
return NodeData.Provider.Plugin.Container.Resolve<TViewModel>(args: new object[] {this, nodeScript});
return Container.Resolve<TViewModel>(args: new object[] {this, nodeScript});
}
/// <summary>

View File

@ -1,14 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<PreserveCompilationContext>false</PreserveCompilationContext>
<Platforms>x64</Platforms>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="LiteDB" />
<PackageReference Include="Serilog" />
<PackageReference Include="Newtonsoft.Json" />
<PackageReference Include="LiteDB" Version="5.0.18" />
<PackageReference Include="Serilog" Version="3.1.1" />
</ItemGroup>
</Project>

View File

@ -11,7 +11,7 @@ public class QueuedActionEntity
}
public Guid Id { get; set; }
public string Type { get; set; } = string.Empty;
public string Type { get; set; }
public DateTimeOffset CreatedAt { get; set; }
public Dictionary<string, object> Parameters { get; set; }

View File

@ -6,6 +6,6 @@ public class ReleaseEntity
{
public Guid Id { get; set; }
public string Version { get; set; } = string.Empty;
public string Version { get; set; }
public DateTimeOffset? InstalledAt { get; set; }
}

View File

@ -6,7 +6,7 @@ public class ScriptConfigurationEntity
{
public Guid Id { get; set; }
public string Name { get; set; } = string.Empty;
public string ScriptingProviderId { get; set; } = string.Empty;
public string? ScriptContent { get; set; }
public string Name { get; set; }
public string ScriptingProviderId { get; set; }
public string ScriptContent { get; set; }
}

View File

@ -24,6 +24,6 @@ public class PluginEntity
/// </summary>
public class PluginFeatureEntity
{
public string Type { get; set; } = string.Empty;
public string Type { get; set; }
public bool IsEnabled { get; set; }
}

View File

@ -10,6 +10,6 @@ public class PluginSettingEntity
public Guid Id { get; set; }
public Guid PluginGuid { get; set; }
public string Name { get; set; } = string.Empty;
public string Value { get; set; } = string.Empty;
public string Name { get; set; }
public string Value { get; set; }
}

View File

@ -0,0 +1,8 @@
using System.Collections.Generic;
namespace Artemis.Storage.Entities.Profile.Abstract;
public abstract class DataModelConditionPartEntity
{
public List<DataModelConditionPartEntity> Children { get; set; }
}

View File

@ -8,8 +8,8 @@ public abstract class RenderElementEntity
public Guid Id { get; set; }
public Guid ParentId { get; set; }
public List<LayerEffectEntity> LayerEffects { get; set; } = new();
public List<LayerEffectEntity> LayerEffects { get; set; }
public IConditionEntity? DisplayCondition { get; set; }
public TimelineEntity? Timeline { get; set; }
public IConditionEntity DisplayCondition { get; set; }
public TimelineEntity Timeline { get; set; }
}

View File

@ -1,3 +1,5 @@
namespace Artemis.Storage.Entities.Profile.AdaptionHints;
public interface IAdaptionHintEntity;
public interface IAdaptionHintEntity
{
}

View File

@ -2,4 +2,6 @@
namespace Artemis.Storage.Entities.Profile.Conditions;
public class AlwaysOnConditionEntity : IConditionEntity;
public class AlwaysOnConditionEntity : IConditionEntity
{
}

View File

@ -8,6 +8,6 @@ public class EventConditionEntity : IConditionEntity
public int TriggerMode { get; set; }
public int OverlapMode { get; set; }
public int ToggleOffMode { get; set; }
public DataModelPathEntity? EventPath { get; set; }
public NodeScriptEntity? Script { get; set; }
public DataModelPathEntity EventPath { get; set; }
public NodeScriptEntity Script { get; set; }
}

View File

@ -1,3 +1,5 @@
namespace Artemis.Storage.Entities.Profile.Abstract;
public interface IConditionEntity;
public interface IConditionEntity
{
}

View File

@ -2,4 +2,6 @@
namespace Artemis.Storage.Entities.Profile.Conditions;
public class PlayOnceConditionEntity : IConditionEntity;
public class PlayOnceConditionEntity : IConditionEntity
{
}

View File

@ -7,5 +7,5 @@ public class StaticConditionEntity : IConditionEntity
{
public int PlayMode { get; set; }
public int StopMode { get; set; }
public NodeScriptEntity? Script { get; set; }
public NodeScriptEntity Script { get; set; }
}

View File

@ -4,6 +4,7 @@ namespace Artemis.Storage.Entities.Profile.DataBindings;
public class DataBindingEntity
{
public string Identifier { get; set; }
public bool IsEnabled { get; set; }
public NodeScriptEntity? NodeScript { get; set; }
public NodeScriptEntity NodeScript { get; set; }
}

View File

@ -2,7 +2,7 @@
public class DataModelPathEntity
{
public string Path { get; set; } = string.Empty;
public string? DataModelId { get; set; }
public string? Type { get; set; }
public string Path { get; set; }
public string DataModelId { get; set; }
public string Type { get; set; }
}

View File

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using Artemis.Storage.Entities.Profile.Abstract;
using LiteDB;
@ -6,13 +7,18 @@ namespace Artemis.Storage.Entities.Profile;
public class FolderEntity : RenderElementEntity
{
public FolderEntity()
{
LayerEffects = new List<LayerEffectEntity>();
}
public int Order { get; set; }
public string? Name { get; set; }
public string Name { get; set; }
public bool IsExpanded { get; set; }
public bool Suspended { get; set; }
[BsonRef("ProfileEntity")]
public ProfileEntity Profile { get; set; } = null!;
public ProfileEntity Profile { get; set; }
public Guid ProfileId { get; set; }
}

View File

@ -6,6 +6,6 @@ public class KeyframeEntity
{
public TimeSpan Position { get; set; }
public int Timeline { get; set; }
public string Value { get; set; } = string.Empty;
public string Value { get; set; }
public int EasingFunction { get; set; }
}

View File

@ -2,8 +2,8 @@
public class LayerBrushEntity
{
public string ProviderId { get; set; } = string.Empty;
public string BrushType { get; set; } = string.Empty;
public string ProviderId { get; set; }
public string BrushType { get; set; }
public PropertyGroupEntity? PropertyGroup { get; set; }
public PropertyGroupEntity PropertyGroup { get; set; }
}

View File

@ -2,11 +2,11 @@
public class LayerEffectEntity
{
public string ProviderId { get; set; } = string.Empty;
public string EffectType { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty;
public string ProviderId { get; set; }
public string EffectType { get; set; }
public string Name { get; set; }
public bool HasBeenRenamed { get; set; }
public int Order { get; set; }
public PropertyGroupEntity? PropertyGroup { get; set; }
public PropertyGroupEntity PropertyGroup { get; set; }
}

View File

@ -12,21 +12,22 @@ public class LayerEntity : RenderElementEntity
{
Leds = new List<LedEntity>();
AdaptionHints = new List<IAdaptionHintEntity>();
LayerEffects = new List<LayerEffectEntity>();
}
public int Order { get; set; }
public string? Name { get; set; }
public string Name { get; set; }
public bool Suspended { get; set; }
public List<LedEntity> Leds { get; set; }
public List<IAdaptionHintEntity> AdaptionHints { get; set; }
public PropertyGroupEntity? GeneralPropertyGroup { get; set; }
public PropertyGroupEntity? TransformPropertyGroup { get; set; }
public LayerBrushEntity? LayerBrush { get; set; }
public PropertyGroupEntity GeneralPropertyGroup { get; set; }
public PropertyGroupEntity TransformPropertyGroup { get; set; }
public LayerBrushEntity LayerBrush { get; set; }
[BsonRef("ProfileEntity")]
public ProfileEntity Profile { get; set; } = null!;
public ProfileEntity Profile { get; set; }
public Guid ProfileId { get; set; }
}

View File

@ -5,8 +5,8 @@ namespace Artemis.Storage.Entities.Profile;
public class LedEntity
{
public string LedName { get; set; } = string.Empty;
public string DeviceIdentifier { get; set; } = string.Empty;
public string LedName { get; set; }
public string DeviceIdentifier { get; set; }
public int? PhysicalLayout { get; set; }
@ -14,7 +14,7 @@ public class LedEntity
private sealed class LedEntityEqualityComparer : IEqualityComparer<LedEntity>
{
public bool Equals(LedEntity? x, LedEntity? y)
public bool Equals(LedEntity x, LedEntity y)
{
if (ReferenceEquals(x, y))
return true;

View File

@ -20,12 +20,12 @@ public class NodeConnectionEntity
TargetPinId = nodeConnectionEntity.TargetPinId;
}
public string SourceType { get; set; } = string.Empty;
public string SourceType { get; set; }
public Guid SourceNode { get; set; }
public Guid TargetNode { get; set; }
public int SourcePinCollectionId { get; set; }
public int SourcePinId { get; set; }
public string TargetType { get; set; } = string.Empty;
public string TargetType { get; set; }
public int TargetPinCollectionId { get; set; }
public int TargetPinId { get; set; }
}

View File

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
@ -15,7 +15,7 @@ public class NodeEntity
{
Id = nodeEntity.Id;
Type = nodeEntity.Type;
ProviderId = nodeEntity.ProviderId;
PluginId = nodeEntity.PluginId;
Name = nodeEntity.Name;
Description = nodeEntity.Description;
@ -28,15 +28,15 @@ public class NodeEntity
}
public Guid Id { get; set; }
public string Type { get; set; } = string.Empty;
public string ProviderId { get; set; } = string.Empty;
public string Type { get; set; }
public Guid PluginId { get; set; }
public string Name { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public string Name { get; set; }
public string Description { get; set; }
public bool IsExitNode { get; set; }
public double X { get; set; }
public double Y { get; set; }
public string Storage { get; set; } = string.Empty;
public string Storage { get; set; }
public List<NodePinCollectionEntity> PinCollections { get; set; }
}

View File

@ -10,8 +10,8 @@ public class NodeScriptEntity
Connections = new List<NodeConnectionEntity>();
}
public string Name { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public string Name { get; set; }
public string Description { get; set; }
public List<NodeEntity> Nodes { get; set; }
public List<NodeConnectionEntity> Connections { get; set; }

View File

@ -7,7 +7,7 @@ public class ProfileCategoryEntity
{
public Guid Id { get; set; }
public string Name { get; set; } = string.Empty;
public string Name { get; set; }
public bool IsCollapsed { get; set; }
public bool IsSuspended { get; set; }
public int Order { get; set; }

View File

@ -5,8 +5,8 @@ namespace Artemis.Storage.Entities.Profile;
public class ProfileConfigurationEntity
{
public string Name { get; set; } = string.Empty;
public string? MaterialIcon { get; set; }
public string Name { get; set; }
public string MaterialIcon { get; set; }
public Guid FileIconId { get; set; }
public int IconType { get; set; }
public bool IconFill { get; set; }
@ -14,17 +14,16 @@ public class ProfileConfigurationEntity
public bool IsSuspended { get; set; }
public int ActivationBehaviour { get; set; }
public NodeScriptEntity? ActivationCondition { get; set; }
public NodeScriptEntity ActivationCondition { get; set; }
public int HotkeyMode { get; set; }
public ProfileConfigurationHotkeyEntity? EnableHotkey { get; set; }
public ProfileConfigurationHotkeyEntity? DisableHotkey { get; set; }
public ProfileConfigurationHotkeyEntity EnableHotkey { get; set; }
public ProfileConfigurationHotkeyEntity DisableHotkey { get; set; }
public string? ModuleId { get; set; }
public string ModuleId { get; set; }
public Guid ProfileCategoryId { get; set; }
public Guid ProfileId { get; set; }
public bool FadeInAndOut { get; set; }
public int Version { get; set; }
}

View File

@ -16,7 +16,7 @@ public class ProfileEntity
public Guid Id { get; set; }
public string Name { get; set; } = string.Empty;
public string Name { get; set; }
public bool IsFreshImport { get; set; }
public List<FolderEntity> Folders { get; set; }
@ -28,7 +28,7 @@ public class ProfileEntity
Guid oldGuid = Id;
Id = guid;
FolderEntity? rootFolder = Folders.FirstOrDefault(f => f.ParentId == oldGuid);
FolderEntity rootFolder = Folders.FirstOrDefault(f => f.ParentId == oldGuid);
if (rootFolder != null)
rootFolder.ParentId = Id;
}

View File

@ -5,10 +5,10 @@ namespace Artemis.Storage.Entities.Profile;
public class PropertyEntity
{
public string Identifier { get; set; } = string.Empty;
public string Value { get; set; } = string.Empty;
public string Identifier { get; set; }
public string Value { get; set; }
public bool KeyframesEnabled { get; set; }
public DataBindingEntity? DataBinding { get; set; }
public DataBindingEntity DataBinding { get; set; }
public List<KeyframeEntity> KeyframeEntities { get; set; } = new();
}

View File

@ -4,7 +4,7 @@ namespace Artemis.Storage.Entities.Profile;
public class PropertyGroupEntity
{
public string Identifier { get; set; } = string.Empty;
public string Identifier { get; set; }
public List<PropertyEntity> Properties { get; set; } = new();
public List<PropertyGroupEntity> PropertyGroups { get; set; } = new();
}

View File

@ -11,8 +11,8 @@ public class DeviceEntity
Categories = new List<int>();
}
public string Id { get; set; } = string.Empty;
public string DeviceProvider { get; set; } = string.Empty;
public string Id { get; set; }
public string DeviceProvider { get; set; }
public float X { get; set; }
public float Y { get; set; }
public float Rotation { get; set; }
@ -24,9 +24,9 @@ public class DeviceEntity
public bool IsEnabled { get; set; }
public int PhysicalLayout { get; set; }
public string? LogicalLayout { get; set; }
public string? LayoutType { get; set; }
public string? LayoutParameter { get; set; }
public string LogicalLayout { get; set; }
public string LayoutType { get; set; }
public string LayoutParameter { get; set; }
public List<DeviceInputIdentifierEntity> InputIdentifiers { get; set; }
public List<InputMappingEntity> InputMappings { get; set; }
@ -41,6 +41,6 @@ public class InputMappingEntity
public class DeviceInputIdentifierEntity
{
public string InputProvider { get; set; } = string.Empty;
public object Identifier { get; set; } = string.Empty;
public string InputProvider { get; set; }
public object Identifier { get; set; }
}

View File

@ -10,12 +10,13 @@ public class EntryEntity
public long EntryId { get; set; }
public int EntryType { get; set; }
public string Author { get; set; } = string.Empty;
public string Author { get; set; }
public string Name { get; set; } = string.Empty;
public string Summary { get; set; } = string.Empty;
public long ReleaseId { get; set; }
public string ReleaseVersion { get; set; } = string.Empty;
public string ReleaseVersion { get; set; }
public DateTimeOffset InstalledAt { get; set; }
public Dictionary<string,object>? Metadata { get; set; }
public Dictionary<string,object> Metadata { get; set; }
}

View File

@ -1,9 +0,0 @@
using Newtonsoft.Json.Linq;
namespace Artemis.Storage.Migrations;
public interface IProfileMigration
{
int Version { get; }
void Migrate(JObject profileJson);
}

View File

@ -1,6 +1,6 @@
using LiteDB;
namespace Artemis.Storage.Migrations;
namespace Artemis.Storage.Migrations.Interfaces;
public interface IStorageMigration
{

View File

@ -1,8 +1,9 @@
using System.Collections.Generic;
using System.Linq;
using Artemis.Storage.Migrations.Interfaces;
using LiteDB;
namespace Artemis.Storage.Migrations.Storage;
namespace Artemis.Storage.Migrations;
public class M0020AvaloniaReset : IStorageMigration
{

View File

@ -3,17 +3,18 @@ using System.Collections.Generic;
using System.Linq;
using Artemis.Storage.Entities.Profile;
using Artemis.Storage.Entities.Profile.Nodes;
using Artemis.Storage.Migrations.Interfaces;
using LiteDB;
namespace Artemis.Storage.Migrations.Storage;
namespace Artemis.Storage.Migrations;
public class M0021GradientNodes : IStorageMigration
{
private void MigrateDataBinding(PropertyEntity property)
{
NodeScriptEntity? script = property.DataBinding?.NodeScript;
NodeEntity? exitNode = script?.Nodes.FirstOrDefault(s => s.IsExitNode);
if (script == null || exitNode == null)
NodeScriptEntity script = property.DataBinding.NodeScript;
NodeEntity exitNode = script.Nodes.FirstOrDefault(s => s.IsExitNode);
if (exitNode == null)
return;
// Create a new node at the same position of the exit node
@ -21,7 +22,7 @@ public class M0021GradientNodes : IStorageMigration
{
Id = Guid.NewGuid(),
Type = "ColorGradientNode",
ProviderId = "Artemis.Plugins.Nodes.General.GeneralNodesProvider-d9e1ee78",
PluginId = Guid.Parse("ffffffff-ffff-ffff-ffff-ffffffffffff"),
Name = "Color Gradient",
Description = "Outputs a color gradient with the given colors",
X = exitNode.X,
@ -58,11 +59,8 @@ public class M0021GradientNodes : IStorageMigration
exitNode.Y += 30;
}
private void MigrateDataBinding(PropertyGroupEntity? propertyGroup)
private void MigrateDataBinding(PropertyGroupEntity propertyGroup)
{
if (propertyGroup == null)
return;
foreach (PropertyGroupEntity propertyGroupPropertyGroup in propertyGroup.PropertyGroups)
MigrateDataBinding(propertyGroupPropertyGroup);
@ -82,7 +80,7 @@ public class M0021GradientNodes : IStorageMigration
foreach (ProfileEntity profileEntity in profiles)
{
foreach (LayerEntity layer in profileEntity.Layers.Where(le => le.LayerBrush != null))
MigrateDataBinding(layer.LayerBrush?.PropertyGroup);
MigrateDataBinding(layer.LayerBrush.PropertyGroup);
repository.Update(profileEntity);
}

View File

@ -3,13 +3,14 @@ using Artemis.Storage.Entities.Profile;
using Artemis.Storage.Entities.Profile.Abstract;
using Artemis.Storage.Entities.Profile.Conditions;
using Artemis.Storage.Entities.Profile.Nodes;
using Artemis.Storage.Migrations.Interfaces;
using LiteDB;
namespace Artemis.Storage.Migrations.Storage;
namespace Artemis.Storage.Migrations;
public class M0022TransitionNodes : IStorageMigration
{
private void MigrateNodeScript(NodeScriptEntity? nodeScript)
private void MigrateNodeScript(NodeScriptEntity nodeScript)
{
if (nodeScript == null)
return;
@ -27,7 +28,7 @@ public class M0022TransitionNodes : IStorageMigration
}
}
private void MigratePropertyGroup(PropertyGroupEntity? propertyGroup)
private void MigratePropertyGroup(PropertyGroupEntity propertyGroup)
{
if (propertyGroup == null)
return;
@ -38,7 +39,7 @@ public class M0022TransitionNodes : IStorageMigration
MigrateNodeScript(property.DataBinding?.NodeScript);
}
private void MigrateDisplayCondition(IConditionEntity? conditionEntity)
private void MigrateDisplayCondition(IConditionEntity conditionEntity)
{
if (conditionEntity is EventConditionEntity eventConditionEntity)
MigrateNodeScript(eventConditionEntity.Script);
@ -69,14 +70,14 @@ public class M0022TransitionNodes : IStorageMigration
MigratePropertyGroup(layer.GeneralPropertyGroup);
MigratePropertyGroup(layer.TransformPropertyGroup);
foreach (LayerEffectEntity layerEffectEntity in layer.LayerEffects)
MigratePropertyGroup(layerEffectEntity.PropertyGroup);
MigratePropertyGroup(layerEffectEntity?.PropertyGroup);
MigrateDisplayCondition(layer.DisplayCondition);
}
foreach (FolderEntity folder in profileEntity.Folders)
{
foreach (LayerEffectEntity folderLayerEffect in folder.LayerEffects)
MigratePropertyGroup(folderLayerEffect.PropertyGroup);
MigratePropertyGroup(folderLayerEffect?.PropertyGroup);
MigrateDisplayCondition(folder.DisplayCondition);
}

View File

@ -1,7 +1,8 @@
using System.Collections.Generic;
using Artemis.Storage.Migrations.Interfaces;
using LiteDB;
namespace Artemis.Storage.Migrations.Storage;
namespace Artemis.Storage.Migrations;
public class M0023LayoutProviders : IStorageMigration
{

View File

@ -1,88 +0,0 @@
using Newtonsoft.Json.Linq;
namespace Artemis.Storage.Migrations.Profile;
/// <summary>
/// Migrates nodes to be provider-based.
/// This requires giving them a ProviderId and updating the their namespaces to match the namespace of the new plugin.
/// </summary>
internal class M0001NodeProviders : IProfileMigration
{
/// <inheritdoc />
public int Version => 1;
/// <inheritdoc />
public void Migrate(JObject profileJson)
{
JArray? folders = (JArray?) profileJson["Folders"]?["$values"];
JArray? layers = (JArray?) profileJson["Layers"]?["$values"];
if (folders != null)
{
foreach (JToken folder in folders)
MigrateProfileElement(folder);
}
if (layers != null)
{
foreach (JToken layer in layers)
{
MigrateProfileElement(layer);
MigratePropertyGroup(layer["GeneralPropertyGroup"]);
MigratePropertyGroup(layer["TransformPropertyGroup"]);
MigratePropertyGroup(layer["LayerBrush"]?["PropertyGroup"]);
}
}
}
private void MigrateProfileElement(JToken profileElement)
{
JArray? layerEffects = (JArray?) profileElement["LayerEffects"]?["$values"];
if (layerEffects != null)
{
foreach (JToken layerEffect in layerEffects)
MigratePropertyGroup(layerEffect["PropertyGroup"]);
}
JToken? displayCondition = profileElement["DisplayCondition"];
if (displayCondition != null)
MigrateNodeScript(displayCondition["Script"]);
}
private void MigratePropertyGroup(JToken? propertyGroup)
{
if (propertyGroup == null || !propertyGroup.HasValues)
return;
JArray? properties = (JArray?) propertyGroup["Properties"]?["$values"];
JArray? propertyGroups = (JArray?) propertyGroup["PropertyGroups"]?["$values"];
if (properties != null)
{
foreach (JToken property in properties)
MigrateNodeScript(property["DataBinding"]?["NodeScript"]);
}
if (propertyGroups != null)
{
foreach (JToken childPropertyGroup in propertyGroups)
MigratePropertyGroup(childPropertyGroup);
}
}
private void MigrateNodeScript(JToken? nodeScript)
{
if (nodeScript == null || !nodeScript.HasValues)
return;
JArray? nodes = (JArray?) nodeScript["Nodes"]?["$values"];
if (nodes == null)
return;
foreach (JToken node in nodes)
{
node["Type"] = node["Type"]?.Value<string>()?.Replace("Artemis.VisualScripting.Nodes", "Artemis.Plugins.Nodes.General.Nodes");
node["ProviderId"] = "Artemis.Plugins.Nodes.General.GeneralNodesProvider-d9e1ee78";
}
}
}

View File

@ -1,100 +0,0 @@
using System.Collections.Generic;
using Artemis.Storage.Entities.Profile;
using LiteDB;
namespace Artemis.Storage.Migrations.Storage;
public class M0024NodeProviders : IStorageMigration
{
public int UserVersion => 24;
public void Apply(LiteRepository repository)
{
List<ProfileCategoryEntity> profileCategories = repository.Query<ProfileCategoryEntity>().ToList();
foreach (ProfileCategoryEntity profileCategory in profileCategories)
{
foreach (ProfileConfigurationEntity profileConfigurationEntity in profileCategory.ProfileConfigurations)
{
profileConfigurationEntity.Version = 1;
}
repository.Update(profileCategory);
}
ILiteCollection<BsonDocument> collection = repository.Database.GetCollection("ProfileEntity");
foreach (BsonDocument profileBson in collection.FindAll())
{
BsonArray? folders = profileBson["Folders"]?.AsArray;
BsonArray? layers = profileBson["Layers"]?.AsArray;
if (folders != null)
{
foreach (BsonValue folder in folders)
MigrateProfileElement(folder.AsDocument);
}
if (layers != null)
{
foreach (BsonValue layer in layers)
{
MigrateProfileElement(layer.AsDocument);
MigratePropertyGroup(layer.AsDocument["GeneralPropertyGroup"].AsDocument);
MigratePropertyGroup(layer.AsDocument["TransformPropertyGroup"].AsDocument);
MigratePropertyGroup(layer.AsDocument["LayerBrush"]?["PropertyGroup"].AsDocument);
}
}
collection.Update(profileBson);
}
}
private void MigrateProfileElement(BsonDocument profileElement)
{
BsonArray? layerEffects = profileElement["LayerEffects"]?.AsArray;
if (layerEffects != null)
{
foreach (BsonValue layerEffect in layerEffects)
MigratePropertyGroup(layerEffect.AsDocument["PropertyGroup"].AsDocument);
}
BsonValue? displayCondition = profileElement["DisplayCondition"];
if (displayCondition != null)
MigrateNodeScript(displayCondition.AsDocument["Script"].AsDocument);
}
private void MigratePropertyGroup(BsonDocument? propertyGroup)
{
if (propertyGroup == null || propertyGroup.Keys.Count == 0)
return;
BsonArray? properties = propertyGroup["Properties"]?.AsArray;
BsonArray? propertyGroups = propertyGroup["PropertyGroups"]?.AsArray;
if (properties != null)
{
foreach (BsonValue property in properties)
MigrateNodeScript(property.AsDocument["DataBinding"]?["NodeScript"]?.AsDocument);
}
if (propertyGroups != null)
{
foreach (BsonValue childPropertyGroup in propertyGroups)
MigratePropertyGroup(childPropertyGroup.AsDocument);
}
}
private void MigrateNodeScript(BsonDocument? nodeScript)
{
if (nodeScript == null || nodeScript.Keys.Count == 0)
return;
BsonArray? nodes = nodeScript["Nodes"]?.AsArray;
if (nodes == null)
return;
foreach (BsonValue node in nodes)
{
node.AsDocument["Type"] = node.AsDocument["Type"]?.AsString?.Replace("Artemis.VisualScripting.Nodes", "Artemis.Plugins.Nodes.General.Nodes");
node.AsDocument["ProviderId"] = "Artemis.Plugins.Nodes.General.GeneralNodesProvider-d9e1ee78";
}
}
}

View File

@ -25,7 +25,7 @@ internal class DeviceRepository : IDeviceRepository
_repository.Delete<DeviceEntity>(deviceEntity.Id);
}
public DeviceEntity? Get(string id)
public DeviceEntity Get(string id)
{
return _repository.FirstOrDefault<DeviceEntity>(s => s.Id == id);
}

View File

@ -27,12 +27,12 @@ internal class EntryRepository : IEntryRepository
_repository.Delete<EntryEntity>(entryEntity.Id);
}
public EntryEntity? Get(Guid id)
public EntryEntity Get(Guid id)
{
return _repository.FirstOrDefault<EntryEntity>(s => s.Id == id);
}
public EntryEntity? GetByEntryId(long entryId)
public EntryEntity GetByEntryId(long entryId)
{
return _repository.FirstOrDefault<EntryEntity>(s => s.EntryId == entryId);
}

View File

@ -7,7 +7,7 @@ public interface IDeviceRepository : IRepository
{
void Add(DeviceEntity deviceEntity);
void Remove(DeviceEntity deviceEntity);
DeviceEntity? Get(string id);
DeviceEntity Get(string id);
List<DeviceEntity> GetAll();
void Save(DeviceEntity deviceEntity);
void Save(IEnumerable<DeviceEntity> deviceEntities);

View File

@ -8,8 +8,8 @@ public interface IEntryRepository : IRepository
{
void Add(EntryEntity entryEntity);
void Remove(EntryEntity entryEntity);
EntryEntity? Get(Guid id);
EntryEntity? GetByEntryId(long entryId);
EntryEntity Get(Guid id);
EntryEntity GetByEntryId(long entryId);
List<EntryEntity> GetAll();
void Save(EntryEntity entryEntity);
void Save(IEnumerable<EntryEntity> entryEntities);

View File

@ -6,12 +6,12 @@ namespace Artemis.Storage.Repositories.Interfaces;
public interface IPluginRepository : IRepository
{
void AddPlugin(PluginEntity pluginEntity);
PluginEntity? GetPluginByGuid(Guid pluginGuid);
PluginEntity GetPluginByGuid(Guid pluginGuid);
void SavePlugin(PluginEntity pluginEntity);
void AddSetting(PluginSettingEntity pluginSettingEntity);
PluginSettingEntity? GetSettingByGuid(Guid pluginGuid);
PluginSettingEntity? GetSettingByNameAndGuid(string name, Guid pluginGuid);
PluginSettingEntity GetSettingByGuid(Guid pluginGuid);
PluginSettingEntity GetSettingByNameAndGuid(string name, Guid pluginGuid);
void SaveSetting(PluginSettingEntity pluginSettingEntity);
void RemoveSettings(Guid pluginGuid);
}

View File

@ -10,8 +10,8 @@ public interface IProfileCategoryRepository : IRepository
void Add(ProfileCategoryEntity profileCategoryEntity);
void Remove(ProfileCategoryEntity profileCategoryEntity);
List<ProfileCategoryEntity> GetAll();
ProfileCategoryEntity? Get(Guid id);
Stream? GetProfileIconStream(Guid id);
ProfileCategoryEntity Get(Guid id);
Stream GetProfileIconStream(Guid id);
void SaveProfileIconStream(ProfileConfigurationEntity profileConfigurationEntity, Stream stream);
ProfileCategoryEntity IsUnique(string name, Guid? id);
void Save(ProfileCategoryEntity profileCategoryEntity);

View File

@ -9,6 +9,6 @@ public interface IProfileRepository : IRepository
void Add(ProfileEntity profileEntity);
void Remove(ProfileEntity profileEntity);
List<ProfileEntity> GetAll();
ProfileEntity? Get(Guid id);
ProfileEntity Get(Guid id);
void Save(ProfileEntity profileEntity);
}

View File

@ -1,3 +1,5 @@
namespace Artemis.Storage.Repositories.Interfaces;
public interface IRepository;
public interface IRepository
{
}

View File

@ -21,7 +21,7 @@ internal class PluginRepository : IPluginRepository
_repository.Insert(pluginEntity);
}
public PluginEntity? GetPluginByGuid(Guid pluginGuid)
public PluginEntity GetPluginByGuid(Guid pluginGuid)
{
return _repository.FirstOrDefault<PluginEntity>(p => p.Id == pluginGuid);
}
@ -29,6 +29,7 @@ internal class PluginRepository : IPluginRepository
public void SavePlugin(PluginEntity pluginEntity)
{
_repository.Upsert(pluginEntity);
_repository.Database.Checkpoint();
}
public void AddSetting(PluginSettingEntity pluginSettingEntity)
@ -36,12 +37,12 @@ internal class PluginRepository : IPluginRepository
_repository.Insert(pluginSettingEntity);
}
public PluginSettingEntity? GetSettingByGuid(Guid pluginGuid)
public PluginSettingEntity GetSettingByGuid(Guid pluginGuid)
{
return _repository.FirstOrDefault<PluginSettingEntity>(p => p.PluginGuid == pluginGuid);
}
public PluginSettingEntity? GetSettingByNameAndGuid(string name, Guid pluginGuid)
public PluginSettingEntity GetSettingByNameAndGuid(string name, Guid pluginGuid)
{
return _repository.FirstOrDefault<PluginSettingEntity>(p => p.Name == name && p.PluginGuid == pluginGuid);
}

View File

@ -34,7 +34,7 @@ internal class ProfileCategoryRepository : IProfileCategoryRepository
return _repository.Query<ProfileCategoryEntity>().ToList();
}
public ProfileCategoryEntity? Get(Guid id)
public ProfileCategoryEntity Get(Guid id)
{
return _repository.FirstOrDefault<ProfileCategoryEntity>(p => p.Id == id);
}
@ -52,7 +52,7 @@ internal class ProfileCategoryRepository : IProfileCategoryRepository
_repository.Upsert(profileCategoryEntity);
}
public Stream? GetProfileIconStream(Guid id)
public Stream GetProfileIconStream(Guid id)
{
if (!_profileIcons.Exists(id))
return null;

View File

@ -31,7 +31,7 @@ internal class ProfileRepository : IProfileRepository
return _repository.Query<ProfileEntity>().ToList();
}
public ProfileEntity? Get(Guid id)
public ProfileEntity Get(Guid id)
{
return _repository.FirstOrDefault<ProfileEntity>(p => p.Id == id);
}

View File

@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Artemis.Storage.Migrations;
using Artemis.Storage.Migrations.Interfaces;
using LiteDB;
using Serilog;

View File

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>net8.0</TargetFramework>
@ -10,17 +10,17 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Avalonia" />
<PackageReference Include="Avalonia" Version="11.0.9" />
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" />
<PackageReference Include="Avalonia.Controls.ItemsRepeater" />
<PackageReference Include="Avalonia.ReactiveUI" />
<PackageReference Include="Avalonia.Xaml.Behaviors" />
<PackageReference Include="DynamicData" />
<PackageReference Include="FluentAvaloniaUI" />
<PackageReference Include="Material.Icons.Avalonia" />
<PackageReference Include="ReactiveUI" />
<PackageReference Include="ReactiveUI.Validation" />
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.0.9" />
<PackageReference Include="Avalonia.Controls.ItemsRepeater" Version="11.0.9" />
<PackageReference Include="Avalonia.ReactiveUI" Version="11.0.9" />
<PackageReference Include="Avalonia.Xaml.Behaviors" Version="11.0.6" />
<PackageReference Include="DynamicData" Version="8.3.27" />
<PackageReference Include="FluentAvaloniaUI" Version="2.0.5" />
<PackageReference Include="Material.Icons.Avalonia" Version="2.1.0" />
<PackageReference Include="ReactiveUI" Version="19.5.41" />
<PackageReference Include="ReactiveUI.Validation" Version="3.1.7" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Artemis.Core\Artemis.Core.csproj" />

View File

@ -18,20 +18,16 @@ internal class DataModelUIService : IDataModelUIService
private readonly IContainer _container;
private readonly List<DataModelVisualizationRegistration> _registeredDataModelDisplays;
private readonly List<DataModelVisualizationRegistration> _registeredDataModelEditors;
private readonly PluginSetting<bool> _showFullPaths;
private readonly PluginSetting<bool> _showDataModelValues;
public DataModelUIService(IDataModelService dataModelService, IContainer container, ISettingsService settingsService)
public DataModelUIService(IDataModelService dataModelService, IContainer container)
{
_dataModelService = dataModelService;
_container = container;
_registeredDataModelEditors = new List<DataModelVisualizationRegistration>();
_registeredDataModelDisplays = new List<DataModelVisualizationRegistration>();
RegisteredDataModelEditors = new ReadOnlyCollection<DataModelVisualizationRegistration>(_registeredDataModelEditors);
RegisteredDataModelDisplays = new ReadOnlyCollection<DataModelVisualizationRegistration>(_registeredDataModelDisplays);
ShowFullPaths = settingsService.GetSetting("ProfileEditor.ShowFullPaths", true);
ShowDataModelValues = settingsService.GetSetting("ProfileEditor.ShowDataModelValues", false);
}
private DataModelInputViewModel InstantiateDataModelInputViewModel(DataModelVisualizationRegistration registration, DataModelPropertyAttribute? description, object? initialValue)
@ -47,9 +43,7 @@ internal class DataModelUIService : IDataModelUIService
public IReadOnlyCollection<DataModelVisualizationRegistration> RegisteredDataModelEditors { get; }
public IReadOnlyCollection<DataModelVisualizationRegistration> RegisteredDataModelDisplays { get; }
public PluginSetting<bool> ShowFullPaths { get; }
public PluginSetting<bool> ShowDataModelValues { get; }
public DataModelPropertiesViewModel GetMainDataModelVisualization()
{
DataModelPropertiesViewModel viewModel = new(null, null, null);

View File

@ -104,14 +104,4 @@ public interface IDataModelUIService : IArtemisSharedUIService
/// <param name="updateCallback">A function to call whenever the input was updated (submitted or not)</param>
/// <returns>The most appropriate input view model for the provided <paramref name="propertyType" /></returns>
DataModelInputViewModel? GetDataModelInputViewModel(Type propertyType, DataModelPropertyAttribute? description, object? initialValue, Action<object?, bool> updateCallback);
/// <summary>
/// Gets a boolean indicating whether or not to show full paths when displaying data model paths.
/// </summary>
PluginSetting<bool> ShowFullPaths { get; }
/// <summary>
/// Gets a boolean indicating whether or not to show values when displaying data model paths.
/// </summary>
PluginSetting<bool> ShowDataModelValues { get; }
}

View File

@ -27,7 +27,6 @@ public class AddNode : INodeEditorCommand, IDisposable
/// <inheritdoc />
public void Dispose()
{
// ReSharper disable once SuspiciousTypeConversion.Global - Provided by plugins
if (_isRemoved && _node is IDisposable disposableNode)
disposableNode.Dispose();
}

View File

@ -29,7 +29,6 @@ public class DeleteNode : INodeEditorCommand, IDisposable
/// <inheritdoc />
public void Dispose()
{
// ReSharper disable once SuspiciousTypeConversion.Global - Provided by plugins
if (_isRemoved && _node is IDisposable disposableNode)
disposableNode.Dispose();
}

View File

@ -87,7 +87,6 @@ public class DuplicateNode : INodeEditorCommand, IDisposable
/// <inheritdoc />
public void Dispose()
{
// ReSharper disable once SuspiciousTypeConversion.Global - Provided by plugins
if (!_executed && _copy is IDisposable disposableNode)
disposableNode.Dispose();
}

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
To use the Avalonia CI feed to get unstable packages, move this file to the root of your solution.
-->
<configuration>
<packageSources>
<add key="AvaloniaCI" value="https://www.myget.org/F/avalonia-ci/api/v2"/>
</packageSources>
</configuration>

View File

@ -21,12 +21,13 @@
</None>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia.Win32" />
<PackageReference Include="Microsoft.Toolkit.Uwp.Notifications" />
<PackageReference Include="Microsoft.Win32" />
<PackageReference Include="Microsoft.Windows.Compatibility" />
<PackageReference Include="RawInput.Sharp" />
<PackageReference Include="SkiaSharp.Vulkan.SharpVk" />
<PackageReference Include="Avalonia.Win32" Version="11.0.9" />
<PackageReference Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.1.3" />
<PackageReference Include="Microsoft.Win32" Version="2.0.1" />
<!-- Note: Do NOT upgrade this compatibility package to 8.X before updating to net8, it WILL break -->
<PackageReference Include="Microsoft.Windows.Compatibility" Version="8.0.2" />
<PackageReference Include="RawInput.Sharp" Version="0.1.3" />
<PackageReference Include="SkiaSharp.Vulkan.SharpVk" Version="2.88.7" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Artemis.Core\Artemis.Core.csproj" />

View File

@ -10,25 +10,26 @@
<ItemGroup>
<ProjectReference Include="..\Artemis.Core\Artemis.Core.csproj" />
<ProjectReference Include="..\Artemis.UI.Shared\Artemis.UI.Shared.csproj" />
<ProjectReference Include="..\Artemis.VisualScripting\Artemis.VisualScripting.csproj" />
<ProjectReference Include="..\Artemis.WebClient.Updating\Artemis.WebClient.Updating.csproj" />
<ProjectReference Include="..\Artemis.WebClient.Workshop\Artemis.WebClient.Workshop.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="AsyncImageLoader.Avalonia" />
<PackageReference Include="Avalonia.AvaloniaEdit" />
<PackageReference Include="Avalonia.Controls.PanAndZoom" />
<PackageReference Include="Avalonia.Desktop" />
<PackageReference Include="Avalonia.Skia.Lottie" />
<PackageReference Include="AvaloniaEdit.TextMate" />
<PackageReference Include="Markdown.Avalonia.Tight" />
<PackageReference Include="Octopus.Octodiff" />
<PackageReference Include="PropertyChanged.SourceGenerator">
<PackageReference Include="AsyncImageLoader.Avalonia" Version="3.2.1" />
<PackageReference Include="Avalonia.AvaloniaEdit" Version="11.0.6" />
<PackageReference Include="Avalonia.Controls.PanAndZoom" Version="11.0.0.2" />
<PackageReference Include="Avalonia.Desktop" Version="11.0.9" />
<PackageReference Include="Avalonia.Skia.Lottie" Version="11.0.0" />
<PackageReference Include="AvaloniaEdit.TextMate" Version="11.0.6" />
<PackageReference Include="Markdown.Avalonia.Tight" Version="11.0.2" />
<PackageReference Include="Octopus.Octodiff" Version="2.0.546" />
<PackageReference Include="PropertyChanged.SourceGenerator" Version="1.1.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Splat.DryIoc" />
<PackageReference Include="TextMateSharp.Grammars" />
<PackageReference Include="Splat.DryIoc" Version="14.8.12" />
<PackageReference Include="TextMateSharp.Grammars" Version="1.0.56" />
</ItemGroup>
<ItemGroup>

View File

@ -10,8 +10,10 @@ using Artemis.UI.Screens.Root;
using Artemis.UI.Shared.DataModelPicker;
using Artemis.UI.Shared.DryIoc;
using Artemis.UI.Shared.Services;
using Artemis.VisualScripting.DryIoc;
using Artemis.WebClient.Updating.DryIoc;
using Artemis.WebClient.Workshop.DryIoc;
using Artemis.WebClient.Workshop.Services;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
@ -47,6 +49,7 @@ public static class ArtemisBootstrapper
_container.RegisterSharedUI();
_container.RegisterUpdatingClient();
_container.RegisterWorkshopClient();
_container.RegisterNoStringEvaluating();
configureServices?.Invoke(_container);
_container.UseDryIocDependencyResolver();

View File

@ -18,13 +18,11 @@ using DynamicData;
using DynamicData.Binding;
using PropertyChanged.SourceGenerator;
using ReactiveUI;
using Serilog;
namespace Artemis.UI.Screens.VisualScripting;
public partial class NodeViewModel : ActivatableViewModelBase
{
private readonly ILogger _logger;
private readonly INodeEditorService _nodeEditorService;
private readonly IWindowService _windowService;
private ObservableAsPropertyHelper<bool>? _hasInputPins;
@ -41,9 +39,8 @@ public partial class NodeViewModel : ActivatableViewModelBase
[Notify] private bool _displayCustomViewModelBelow;
[Notify] private VerticalAlignment _customViewModelVerticalAlignment;
public NodeViewModel(NodeScriptViewModel nodeScriptViewModel, INode node, ILogger logger, INodeVmFactory nodeVmFactory, INodeEditorService nodeEditorService, IWindowService windowService)
public NodeViewModel(NodeScriptViewModel nodeScriptViewModel, INode node, INodeVmFactory nodeVmFactory, INodeEditorService nodeEditorService, IWindowService windowService)
{
_logger = logger;
_nodeEditorService = nodeEditorService;
_windowService = windowService;
NodeScriptViewModel = nodeScriptViewModel;
@ -140,7 +137,25 @@ public partial class NodeViewModel : ActivatableViewModelBase
});
// Set up the custom node VM if needed
SetupCustomNodeViewModel();
if (Node is ICustomViewModelNode customViewModelNode)
{
CustomNodeViewModel = customViewModelNode.GetCustomViewModel(nodeScriptViewModel.NodeScript);
if (customViewModelNode.ViewModelPosition == CustomNodeViewModelPosition.AbovePins)
DisplayCustomViewModelAbove = true;
else if (customViewModelNode.ViewModelPosition == CustomNodeViewModelPosition.BelowPins)
DisplayCustomViewModelBelow = true;
else
{
DisplayCustomViewModelBetween = true;
if (customViewModelNode.ViewModelPosition == CustomNodeViewModelPosition.BetweenPinsTop)
CustomViewModelVerticalAlignment = VerticalAlignment.Top;
else if (customViewModelNode.ViewModelPosition == CustomNodeViewModelPosition.BetweenPinsTop)
CustomViewModelVerticalAlignment = VerticalAlignment.Center;
else
CustomViewModelVerticalAlignment = VerticalAlignment.Bottom;
}
}
});
}
@ -194,35 +209,4 @@ public partial class NodeViewModel : ActivatableViewModelBase
if (Node.BrokenState != null && Node.BrokenStateException != null)
_windowService.ShowExceptionDialog(Node.BrokenState, Node.BrokenStateException);
}
private void SetupCustomNodeViewModel()
{
if (Node is not ICustomViewModelNode customViewModelNode)
return;
try
{
CustomNodeViewModel = customViewModelNode.GetCustomViewModel(NodeScriptViewModel.NodeScript);
}
catch (Exception e)
{
_logger.Error(e, "Failed to instantiate custom node view model");
}
if (customViewModelNode.ViewModelPosition == CustomNodeViewModelPosition.AbovePins)
DisplayCustomViewModelAbove = true;
else if (customViewModelNode.ViewModelPosition == CustomNodeViewModelPosition.BelowPins)
DisplayCustomViewModelBelow = true;
else
{
DisplayCustomViewModelBetween = true;
if (customViewModelNode.ViewModelPosition == CustomNodeViewModelPosition.BetweenPinsTop)
CustomViewModelVerticalAlignment = VerticalAlignment.Top;
else if (customViewModelNode.ViewModelPosition == CustomNodeViewModelPosition.BetweenPinsTop)
CustomViewModelVerticalAlignment = VerticalAlignment.Center;
else
CustomViewModelVerticalAlignment = VerticalAlignment.Bottom;
}
}
}

View File

@ -93,14 +93,6 @@ public partial class ImageSubmissionViewModel : ValidatableViewModelBase
Name = vm.Name;
Description = vm.Description;
// TODO: Just get rid of this stupid mechanism
if (ImageUploadRequest != null)
{
if (Name != null)
ImageUploadRequest.Name = Name;
ImageUploadRequest.Description = Description;
}
return result;
}
}

View File

@ -6,4 +6,5 @@ public interface IRegistrationService : IArtemisUIService
void RegisterBuiltInDataModelInputs();
void RegisterBuiltInPropertyEditors();
void RegisterControllers();
void RegisterBuiltInNodeTypes();
}

View File

@ -1,4 +1,7 @@
using System.Linq;
using System;
using System.Collections;
using System.Linq;
using System.Reflection;
using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Controllers;
@ -10,8 +13,10 @@ using Artemis.UI.Shared.Routing;
using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.ProfileEditor;
using Artemis.UI.Shared.Services.PropertyInput;
using Artemis.VisualScripting.Nodes.Mathematics;
using Avalonia;
using DryIoc;
using SkiaSharp;
namespace Artemis.UI.Services;
@ -21,6 +26,7 @@ public class RegistrationService : IRegistrationService
private readonly IInputService _inputService;
private readonly IContainer _container;
private readonly IRouter _router;
private readonly INodeService _nodeService;
private readonly IPropertyInputService _propertyInputService;
private readonly IWebServerService _webServerService;
private bool _registeredBuiltInPropertyEditors;
@ -30,6 +36,7 @@ public class RegistrationService : IRegistrationService
IInputService inputService,
IPropertyInputService propertyInputService,
IProfileEditorService profileEditorService,
INodeService nodeService,
IDataModelUIService dataModelUIService,
IWebServerService webServerService,
IDeviceLayoutService deviceLayoutService // here to make sure it is instantiated
@ -39,11 +46,13 @@ public class RegistrationService : IRegistrationService
_router = router;
_inputService = inputService;
_propertyInputService = propertyInputService;
_nodeService = nodeService;
_dataModelUIService = dataModelUIService;
_webServerService = webServerService;
CreateCursorResources();
RegisterRoutes();
RegisterBuiltInNodeTypes();
RegisterControllers();
}
@ -96,4 +105,22 @@ public class RegistrationService : IRegistrationService
{
_webServerService.AddController<RemoteController>(Constants.CorePlugin.Features.First().Instance!);
}
public void RegisterBuiltInNodeTypes()
{
_nodeService.RegisterTypeColor(Constants.CorePlugin, typeof(bool), new SKColor(0xFFCD3232));
_nodeService.RegisterTypeColor(Constants.CorePlugin, typeof(string), new SKColor(0xFFFFD700));
_nodeService.RegisterTypeColor(Constants.CorePlugin, typeof(Numeric), new SKColor(0xFF32CD32));
_nodeService.RegisterTypeColor(Constants.CorePlugin, typeof(float), new SKColor(0xFFFF7C00));
_nodeService.RegisterTypeColor(Constants.CorePlugin, typeof(SKColor), new SKColor(0xFFAD3EED));
_nodeService.RegisterTypeColor(Constants.CorePlugin, typeof(IList), new SKColor(0xFFED3E61));
_nodeService.RegisterTypeColor(Constants.CorePlugin, typeof(Enum), new SKColor(0xFF1E90FF));
_nodeService.RegisterTypeColor(Constants.CorePlugin, typeof(ColorGradient), new SKColor(0xFF00B2A9));
foreach (Type nodeType in typeof(SumNumericsNode).Assembly.GetTypes().Where(t => typeof(INode).IsAssignableFrom(t) && t.IsPublic && !t.IsAbstract && !t.IsInterface))
{
if (nodeType.GetCustomAttribute(typeof(NodeAttribute)) != null)
_nodeService.RegisterNodeType(Constants.CorePlugin, nodeType);
}
}
}

View File

@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Platforms>x64</Platforms>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="NoStringEvaluating" Version="2.5.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Artemis.Core\Artemis.Core.csproj" />
<ProjectReference Include="..\Artemis.UI.Shared\Artemis.UI.Shared.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,25 @@
using System.Globalization;
using Artemis.Core;
using Avalonia.Data.Converters;
using Newtonsoft.Json;
namespace Artemis.VisualScripting.Converters;
/// <summary>
/// Converts input into <see cref="Numeric" />.
/// </summary>
public class JsonConverter : IValueConverter
{
/// <inheritdoc />
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
return JsonConvert.SerializeObject(value, Formatting.Indented);
}
/// <inheritdoc />
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
string? json = value?.ToString();
return json == null ? null : JsonConvert.DeserializeObject(json, targetType);
}
}

View File

@ -0,0 +1,27 @@
using System.Globalization;
using Artemis.Core;
using Avalonia.Data.Converters;
namespace Artemis.VisualScripting.Converters;
/// <summary>
/// Converts input into <see cref="Numeric" />.
/// </summary>
public class NumericConverter : IValueConverter
{
/// <inheritdoc />
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is not Numeric numeric)
return value;
object result = Numeric.IsTypeCompatible(targetType) ? numeric.ToType(targetType, NumberFormatInfo.InvariantInfo) : value;
return result;
}
/// <inheritdoc />
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
return new Numeric(value);
}
}

View File

@ -0,0 +1,41 @@
using DryIoc;
using Microsoft.Extensions.ObjectPool;
using NoStringEvaluating;
using NoStringEvaluating.Contract;
using NoStringEvaluating.Models.Values;
using NoStringEvaluating.Services.Cache;
using NoStringEvaluating.Services.Checking;
using NoStringEvaluating.Services.Parsing;
using NoStringEvaluating.Services.Parsing.NodeReaders;
namespace Artemis.VisualScripting.DryIoc;
/// <summary>
/// Provides an extension method to register services onto a DryIoc <see cref="IContainer"/>.
/// </summary>
public static class ContainerExtensions
{
/// <summary>
/// Registers NoStringEvaluating services into the container.
/// </summary>
/// <param name="container">The builder building the current container</param>
public static void RegisterNoStringEvaluating(this IContainer container)
{
// Pooling
container.RegisterInstance(ObjectPool.Create<Stack<InternalEvaluatorValue>>());
container.RegisterInstance(ObjectPool.Create<List<InternalEvaluatorValue>>());
container.RegisterInstance(ObjectPool.Create<ValueKeeperContainer>());
// Parser
container.Register<IFormulaCache, FormulaCache>(Reuse.Singleton);
container.Register<IFunctionReader, FunctionReader>(Reuse.Singleton);
container.Register<IFormulaParser, FormulaParser>(Reuse.Singleton);
// Checker
container.Register<IFormulaChecker, FormulaChecker>(Reuse.Singleton);
// Evaluator
container.Register<INoStringEvaluator, NoStringEvaluator>(Reuse.Singleton);
container.Register<INoStringEvaluatorNullable, NoStringEvaluatorNullable>(Reuse.Singleton);
}
}

View File

@ -0,0 +1,59 @@
using Artemis.Core;
using Artemis.Core.Events;
namespace Artemis.VisualScripting.Nodes.Branching;
[Node("Branch", "Forwards one of two values depending on an input boolean", "Branching", InputType = typeof(object), OutputType = typeof(object))]
public class BooleanBranchNode : Node
{
public BooleanBranchNode()
{
BooleanInput = CreateInputPin<bool>();
TrueInput = CreateInputPin(typeof(object), "True");
FalseInput = CreateInputPin(typeof(object), "False");
Output = CreateOutputPin(typeof(object));
TrueInput.PinConnected += InputPinConnected;
FalseInput.PinConnected += InputPinConnected;
TrueInput.PinDisconnected += InputPinDisconnected;
FalseInput.PinDisconnected += InputPinDisconnected;
}
public InputPin<bool> BooleanInput { get; set; }
public InputPin TrueInput { get; set; }
public InputPin FalseInput { get; set; }
public OutputPin Output { get; set; }
#region Overrides of Node
/// <inheritdoc />
public override void Evaluate()
{
Output.Value = BooleanInput.Value ? TrueInput.Value : FalseInput.Value;
}
#endregion
private void InputPinConnected(object? sender, SingleValueEventArgs<IPin> e)
{
if (TrueInput.ConnectedTo.Any())
ChangeType(TrueInput.ConnectedTo.First().Type);
else if (FalseInput.ConnectedTo.Any())
ChangeType(FalseInput.ConnectedTo.First().Type);
}
private void InputPinDisconnected(object? sender, SingleValueEventArgs<IPin> e)
{
if (!TrueInput.ConnectedTo.Any() && !FalseInput.ConnectedTo.Any())
ChangeType(typeof(object));
}
private void ChangeType(Type type)
{
TrueInput.ChangeType(type);
FalseInput.ChangeType(type);
Output.ChangeType(type);
}
}

Some files were not shown because too many files have changed in this diff Show More