1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2026-01-01 18:23:32 +00:00

Compare commits

...

24 Commits

Author SHA1 Message Date
RobertBeekman
2e600e76b0 Merge branch 'master' into development 2024-02-23 23:26:55 +01:00
RobertBeekman
110b6b90a0 Work around idiotic .NET 8 breaking change 2024-02-23 23:25:22 +01:00
RobertBeekman
aaaae5be52 Revert "Trial and error paths on CIs yaaaay"
This reverts commit e5edc97077077773ecf92e5c256020ea982e2331.
2024-02-23 22:42:31 +01:00
RobertBeekman
e5edc97077 Trial and error paths on CIs yaaaay 2024-02-23 22:39:49 +01:00
RobertBeekman
108b881fd6 CI - Fix Windows RID 2024-02-23 22:18:58 +01:00
RobertBeekman
d48d0acb5b Merge branch 'development' 2024-02-23 22:13:52 +01:00
RobertBeekman
585a2fbe18 Workshop - Fix info about new images not applying 2024-02-23 22:13:46 +01:00
RobertBeekman
27ead861a7 CI - Bump .NET to 8 there as well... 2024-02-23 22:07:33 +01:00
RobertBeekman
ef08000f56 Merge branch 'development' 2024-02-23 21:45:35 +01:00
RobertBeekman
9d955bd983
Merge pull request #837 from Artemis-RGB/feature/cpm
Centralized package management
2024-02-23 21:21:39 +01:00
Robert
d0e354952d Merge branch 'development' into feature/cpm 2024-02-23 21:21:27 +01:00
RobertBeekman
93e74ef8d3
Core - Implemented node providers and removed nodes from the main project 2024-02-23 21:16:08 +01:00
Robert
bcf0b74fcc Clean up 2024-02-23 21:15:34 +01:00
Robert
49ed0205b7 Move migrations to storage, add Storage migration for nodes change 2024-02-23 20:58:32 +01:00
Robert
313b4a0dea Core - Fix node related warnings 2024-02-23 17:41:11 +01:00
Robert
6adcae94b4 Merge branch 'development' into feature/node-providers 2024-02-23 17:36:47 +01:00
Robert
248d4c8e18 Storage - Use nullable reference types
Core - Fix build warnings
2024-02-23 17:35:06 +01:00
RobertBeekman
bfd4a436de Profile service - Migrate profiles before importing them 2024-02-22 21:23:47 +01:00
RobertBeekman
39d7d8132f Core - Added node provider plugin feature type
Visual Scripting - Removed all nodes
2024-02-22 19:42:25 +01:00
Diogo Trindade
f88459fddd
Centralized package management 2024-02-20 21:51:39 +00:00
RobertBeekman
c901bce626 Merge branch 'development' 2024-02-19 22:46:52 +01:00
Robert
00c510cad3 Merge branch 'development' 2024-02-17 15:01:38 +01:00
RobertBeekman
c61e3d289a Merge branch 'development' 2024-02-17 11:54:29 +01:00
RobertBeekman
5184d53d97 Merge branch 'development' 2024-02-13 21:55:23 +01:00
247 changed files with 775 additions and 6681 deletions

View File

@ -16,7 +16,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v2
with:
dotnet-version: "7.0.x"
dotnet-version: "8.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: win10-x64
rid: win-x64
csproj: Windows
- os: ubuntu-latest
@ -60,7 +60,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v2
with:
dotnet-version: '7.0.x'
dotnet-version: '8.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: '7.0.x'
dotnet-version: '8.0.x'
- name: Checkout
uses: actions/checkout@v3
- name: Pack Artemis.Core

View File

@ -36,20 +36,20 @@
</ItemGroup>
<ItemGroup>
<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" />
<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" />
</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.Interfaces;
using Artemis.Storage.Migrations;
using Artemis.Storage.Repositories.Interfaces;
using DryIoc;
@ -36,6 +36,7 @@ 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,7 +240,8 @@ public abstract class LayerPropertyGroup : IDisposable
foreach (LayerPropertyGroup layerPropertyGroup in LayerPropertyGroups)
{
layerPropertyGroup.ApplyToEntity();
PropertyGroupEntity.PropertyGroups.Add(layerPropertyGroup.PropertyGroupEntity);
if (layerPropertyGroup.PropertyGroupEntity != null)
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.LayoutType};
LayoutSelection = new LayoutSelection {Type = DefaultLayoutProvider.LAYOUT_TYPE};
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.LayoutType};
LayoutSelection = new LayoutSelection {Type = DefaultLayoutProvider.LAYOUT_TYPE};
foreach (DeviceInputIdentifierEntity identifierEntity in DeviceEntity.InputIdentifiers)
InputIdentifiers.Add(new ArtemisDeviceInputIdentifier(identifierEntity.InputProvider, identifierEntity.Identifier));
@ -155,6 +155,9 @@ 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,6 +57,9 @@ 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};
brush.LayerBrushEntity = entity ?? new LayerBrushEntity {ProviderId = Provider.Id, BrushType = LayerBrushType.FullName ?? throw new InvalidOperationException()};
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;
LayerEffectEntity.EffectType = GetType().FullName ?? throw new InvalidOperationException();
BaseProperties?.ApplyToEntity();
LayerEffectEntity.PropertyGroup = BaseProperties?.PropertyGroupEntity;
}

View File

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

View File

@ -1,8 +1,14 @@
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
{
public static string LayoutType = "Default";
/// <summary>
/// The layout type of this layout provider.
/// </summary>
public const string LAYOUT_TYPE = "Default";
/// <inheritdoc />
public ArtemisLayout? GetDeviceLayout(ArtemisDevice device)
@ -26,7 +32,7 @@ public class DefaultLayoutProvider : ILayoutProvider
/// <inheritdoc />
public bool IsMatch(ArtemisDevice device)
{
return device.LayoutSelection.Type == LayoutType;
return device.LayoutSelection.Type == LAYOUT_TYPE;
}
/// <summary>
@ -35,7 +41,7 @@ public class DefaultLayoutProvider : ILayoutProvider
/// <param name="device">The device to apply the provider to.</param>
public void ConfigureDevice(ArtemisDevice device)
{
device.LayoutSelection.Type = LayoutType;
device.LayoutSelection.Type = LAYOUT_TYPE;
device.LayoutSelection.Parameter = null;
}
}

View File

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

View File

@ -1,11 +1,9 @@
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;
@ -13,31 +11,8 @@ 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()
{
@ -53,7 +28,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.CorePlugin);
return new TypeColorRegistration(type, new SKColor(255, 255, 255, 255), Constants.CorePluginFeature);
// Come up with a random color based on the type name that should be the same each time
MD5 md5Hasher = MD5.Create();
@ -61,32 +36,7 @@ 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.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);
return new TypeColorRegistration(type, baseColor, Constants.CorePluginFeature);
}
public string ExportScript(NodeScript nodeScript)
@ -103,33 +53,6 @@ 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>
@ -153,21 +76,6 @@ 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,6 +7,9 @@ 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
@ -15,8 +18,11 @@ public static partial class ProcessMonitor
private static Timer? _timer;
private static Dictionary<int, ProcessInfo> _processes = new();
private static readonly Dictionary<int, ProcessInfo> _processes = new();
/// <summary>
/// Gets an immutable array of the current processes.
/// </summary>
public static ImmutableArray<ProcessInfo> Processes
{
get
@ -25,9 +31,17 @@ 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;
@ -40,6 +54,9 @@ public static partial class ProcessMonitor
}
}
/// <summary>
/// Gets a value indicating whether the monitoring has started.
/// </summary>
public static bool IsStarted
{
get
@ -53,7 +70,14 @@ 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
@ -69,6 +93,9 @@ public static partial class ProcessMonitor
#region Methods
/// <summary>
/// Starts monitoring processes.
/// </summary>
public static void Start()
{
lock (LOCK)
@ -87,6 +114,9 @@ public static partial class ProcessMonitor
}
}
/// <summary>
/// Stops monitoring processes.
/// </summary>
public static void Stop()
{
lock (LOCK)
@ -100,7 +130,7 @@ public static partial class ProcessMonitor
FreeBuffer();
}
}
/// <summary>
/// Returns whether the specified process is running
/// </summary>
@ -111,7 +141,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));
@ -130,19 +160,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;
}
@ -152,7 +182,10 @@ 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)
@ -161,7 +194,10 @@ public static partial class ProcessMonitor
{
ProcessStopped?.Invoke(null, new ProcessEventArgs(processInfo));
}
catch { /* Subscribers are idiots! */ }
catch
{
/* Subscribers are idiots! */
}
}
#endregion

View File

@ -42,6 +42,7 @@ 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,8 +8,10 @@ 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;
@ -24,9 +26,10 @@ 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;
@ -35,13 +38,15 @@ internal class ProfileService : IProfileService
IPluginManagementService pluginManagementService,
IInputService inputService,
IDeviceService deviceService,
IProfileRepository profileRepository)
IProfileRepository profileRepository,
List<IProfileMigration> profileMigrators)
{
_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;
@ -58,7 +63,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 />
@ -221,7 +226,7 @@ internal class ProfileService : IProfileService
return profileConfiguration.Profile;
}
ProfileEntity profileEntity;
ProfileEntity? profileEntity;
try
{
profileEntity = _profileRepository.Get(profileConfiguration.Entity.ProfileId);
@ -280,7 +285,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;
@ -353,7 +358,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);
@ -461,7 +466,12 @@ internal class ProfileService : IProfileService
await using Stream profileStream = profileEntry.Open();
using StreamReader profileReader = new(profileStream);
ProfileEntity? profileEntity = JsonConvert.DeserializeObject<ProfileEntity>(await profileReader.ReadToEndAsync(), IProfileService.ExportSettings);
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));
if (profileEntity == null)
throw new ArtemisCoreException("Could not import profile, failed to deserialize profile.json");
@ -514,10 +524,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;
}
@ -545,6 +555,21 @@ 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>
/// <typeparam name="T">The type of Web API controller to remove</typeparam>
/// <param name="registration">The registration of the controller to remove.</param>
void RemoveController(WebApiControllerRegistration registration);
/// <summary>

View File

@ -13,16 +13,13 @@ 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.Plugin) {IsInStore = true};
typeRegistration = new NodeTypeRegistration(nodeData, nodeData.Provider) {IsInStore = true};
Registrations.Add(typeRegistration);
}
@ -60,24 +57,12 @@ internal class NodeTypeStore
}
}
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)
public static TypeColorRegistration AddColor(Type type, SKColor color, PluginFeature pluginFeature)
{
TypeColorRegistration typeColorRegistration;
lock (ColorRegistrations)
{
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};
typeColorRegistration = new TypeColorRegistration(type, color, pluginFeature) {IsInStore = true};
ColorRegistrations.Add(typeColorRegistration);
}

View File

@ -9,12 +9,12 @@ namespace Artemis.Core;
/// </summary>
public class NodeTypeRegistration
{
internal NodeTypeRegistration(NodeData nodeData, Plugin plugin)
internal NodeTypeRegistration(NodeData nodeData, PluginFeature pluginFeature)
{
NodeData = nodeData;
Plugin = plugin;
PluginFeature = pluginFeature;
Plugin.Disabled += OnDisabled;
PluginFeature.Disabled += OnDisabled;
}
/// <summary>
@ -23,9 +23,9 @@ public class NodeTypeRegistration
public NodeData NodeData { get; }
/// <summary>
/// Gets the plugin the node is associated with
/// Gets the plugin feature the node is associated with
/// </summary>
public Plugin Plugin { get; }
public PluginFeature PluginFeature { 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 Plugin.Guid == entity.PluginId && NodeData.Type.Name == entity.Type;
return PluginFeature.Id == entity.ProviderId && NodeData.Type.Name == entity.Type;
}
private void OnDisabled(object? sender, EventArgs e)
{
Plugin.Disabled -= OnDisabled;
PluginFeature.Disabled -= OnDisabled;
if (IsInStore)
NodeTypeStore.Remove(this);
}
@ -55,13 +55,13 @@ public class NodeTypeRegistration
/// </summary>
public class TypeColorRegistration
{
internal TypeColorRegistration(Type type, SKColor color, Plugin plugin)
internal TypeColorRegistration(Type type, SKColor color, PluginFeature pluginFeature)
{
Type = type;
Color = color;
Plugin = plugin;
PluginFeature = pluginFeature;
Plugin.Disabled += OnDisabled;
PluginFeature.Disabled += OnDisabled;
}
/// <summary>
@ -80,9 +80,9 @@ public class TypeColorRegistration
public SKColor DarkenedColor => Color.Darken(0.35f);
/// <summary>
/// Gets the plugin type color is associated with
/// Gets the plugin feature this type color is associated with
/// </summary>
public Plugin Plugin { get; }
public PluginFeature PluginFeature { 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)
{
Plugin.Disabled -= OnDisabled;
PluginFeature.Disabled -= OnDisabled;
if (IsInStore)
NodeTypeStore.RemoveColor(this);
}

View File

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

View File

@ -161,6 +161,7 @@ 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();
}
@ -181,6 +182,7 @@ 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();
}
@ -312,7 +314,7 @@ public abstract class NodeScript : CorePropertyChanged, INodeScript
NodeEntity nodeEntity = new()
{
Id = node.Id,
PluginId = NodeTypeStore.GetPlugin(node)?.Guid ?? Constants.CorePlugin.Guid,
ProviderId = node.NodeData?.Provider.Id ?? Constants.CorePluginFeature.Id,
Type = node.GetType().Name,
X = node.X,
Y = node.Y,

View File

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

View File

@ -25,7 +25,9 @@ public abstract class Node<TStorage, TViewModel> : Node<TStorage>, ICustomViewMo
/// <param name="nodeScript"></param>
public virtual TViewModel GetViewModel(NodeScript nodeScript)
{
return Container.Resolve<TViewModel>(args: new object[] {this, 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});
}
/// <summary>

View File

@ -1,12 +1,14 @@
<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" Version="5.0.18" />
<PackageReference Include="Serilog" Version="3.1.1" />
<PackageReference Include="LiteDB" />
<PackageReference Include="Serilog" />
<PackageReference Include="Newtonsoft.Json" />
</ItemGroup>
</Project>

View File

@ -11,7 +11,7 @@ public class QueuedActionEntity
}
public Guid Id { get; set; }
public string Type { get; set; }
public string Type { get; set; } = string.Empty;
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; }
public string Version { get; set; } = string.Empty;
public DateTimeOffset? InstalledAt { get; set; }
}

View File

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

View File

@ -24,6 +24,6 @@ public class PluginEntity
/// </summary>
public class PluginFeatureEntity
{
public string Type { get; set; }
public string Type { get; set; } = string.Empty;
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; }
public string Value { get; set; }
public string Name { get; set; } = string.Empty;
public string Value { get; set; } = string.Empty;
}

View File

@ -1,8 +0,0 @@
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; }
public List<LayerEffectEntity> LayerEffects { get; set; } = new();
public IConditionEntity DisplayCondition { get; set; }
public TimelineEntity Timeline { get; set; }
public IConditionEntity? DisplayCondition { get; set; }
public TimelineEntity? Timeline { get; set; }
}

View File

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

View File

@ -2,6 +2,4 @@
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,5 +1,3 @@
namespace Artemis.Storage.Entities.Profile.Abstract;
public interface IConditionEntity
{
}
public interface IConditionEntity;

View File

@ -2,6 +2,4 @@
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,7 +4,6 @@ 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; }
public string DataModelId { get; set; }
public string Type { get; set; }
public string Path { get; set; } = string.Empty;
public string? DataModelId { get; set; }
public string? Type { get; set; }
}

View File

@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using Artemis.Storage.Entities.Profile.Abstract;
using LiteDB;
@ -7,18 +6,13 @@ 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; }
public ProfileEntity Profile { get; set; } = null!;
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; }
public string Value { get; set; } = string.Empty;
public int EasingFunction { get; set; }
}

View File

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

View File

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

View File

@ -12,22 +12,21 @@ 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; }
public ProfileEntity Profile { get; set; } = null!;
public Guid ProfileId { get; set; }
}

View File

@ -5,8 +5,8 @@ namespace Artemis.Storage.Entities.Profile;
public class LedEntity
{
public string LedName { get; set; }
public string DeviceIdentifier { get; set; }
public string LedName { get; set; } = string.Empty;
public string DeviceIdentifier { get; set; } = string.Empty;
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; }
public string SourceType { get; set; } = string.Empty;
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; }
public string TargetType { get; set; } = string.Empty;
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;
PluginId = nodeEntity.PluginId;
ProviderId = nodeEntity.ProviderId;
Name = nodeEntity.Name;
Description = nodeEntity.Description;
@ -28,15 +28,15 @@ public class NodeEntity
}
public Guid Id { get; set; }
public string Type { get; set; }
public Guid PluginId { get; set; }
public string Type { get; set; } = string.Empty;
public string ProviderId { get; set; } = string.Empty;
public string Name { get; set; }
public string Description { get; set; }
public string Name { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public bool IsExitNode { get; set; }
public double X { get; set; }
public double Y { get; set; }
public string Storage { get; set; }
public string Storage { get; set; } = string.Empty;
public List<NodePinCollectionEntity> PinCollections { get; set; }
}

View File

@ -10,8 +10,8 @@ public class NodeScriptEntity
Connections = new List<NodeConnectionEntity>();
}
public string Name { get; set; }
public string Description { get; set; }
public string Name { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
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; }
public string Name { get; set; } = string.Empty;
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; }
public string MaterialIcon { get; set; }
public string Name { get; set; } = string.Empty;
public string? MaterialIcon { get; set; }
public Guid FileIconId { get; set; }
public int IconType { get; set; }
public bool IconFill { get; set; }
@ -14,16 +14,17 @@ 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; }
public string Name { get; set; } = string.Empty;
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; }
public string Value { get; set; }
public string Identifier { get; set; } = string.Empty;
public string Value { get; set; } = string.Empty;
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; }
public string Identifier { get; set; } = string.Empty;
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; }
public string DeviceProvider { get; set; }
public string Id { get; set; } = string.Empty;
public string DeviceProvider { get; set; } = string.Empty;
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; }
public object Identifier { get; set; }
public string InputProvider { get; set; } = string.Empty;
public object Identifier { get; set; } = string.Empty;
}

View File

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

View File

@ -0,0 +1,9 @@
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.Interfaces;
namespace Artemis.Storage.Migrations;
public interface IStorageMigration
{

View File

@ -0,0 +1,88 @@
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,9 +1,8 @@
using System.Collections.Generic;
using System.Linq;
using Artemis.Storage.Migrations.Interfaces;
using LiteDB;
namespace Artemis.Storage.Migrations;
namespace Artemis.Storage.Migrations.Storage;
public class M0020AvaloniaReset : IStorageMigration
{

View File

@ -3,18 +3,17 @@ 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;
namespace Artemis.Storage.Migrations.Storage;
public class M0021GradientNodes : IStorageMigration
{
private void MigrateDataBinding(PropertyEntity property)
{
NodeScriptEntity script = property.DataBinding.NodeScript;
NodeEntity exitNode = script.Nodes.FirstOrDefault(s => s.IsExitNode);
if (exitNode == null)
NodeScriptEntity? script = property.DataBinding?.NodeScript;
NodeEntity? exitNode = script?.Nodes.FirstOrDefault(s => s.IsExitNode);
if (script == null || exitNode == null)
return;
// Create a new node at the same position of the exit node
@ -22,7 +21,7 @@ public class M0021GradientNodes : IStorageMigration
{
Id = Guid.NewGuid(),
Type = "ColorGradientNode",
PluginId = Guid.Parse("ffffffff-ffff-ffff-ffff-ffffffffffff"),
ProviderId = "Artemis.Plugins.Nodes.General.GeneralNodesProvider-d9e1ee78",
Name = "Color Gradient",
Description = "Outputs a color gradient with the given colors",
X = exitNode.X,
@ -59,8 +58,11 @@ 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);
@ -80,7 +82,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,14 +3,13 @@ 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;
namespace Artemis.Storage.Migrations.Storage;
public class M0022TransitionNodes : IStorageMigration
{
private void MigrateNodeScript(NodeScriptEntity nodeScript)
private void MigrateNodeScript(NodeScriptEntity? nodeScript)
{
if (nodeScript == null)
return;
@ -28,7 +27,7 @@ public class M0022TransitionNodes : IStorageMigration
}
}
private void MigratePropertyGroup(PropertyGroupEntity propertyGroup)
private void MigratePropertyGroup(PropertyGroupEntity? propertyGroup)
{
if (propertyGroup == null)
return;
@ -39,7 +38,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);
@ -70,14 +69,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,8 +1,7 @@
using System.Collections.Generic;
using Artemis.Storage.Migrations.Interfaces;
using LiteDB;
namespace Artemis.Storage.Migrations;
namespace Artemis.Storage.Migrations.Storage;
public class M0023LayoutProviders : IStorageMigration
{

View File

@ -0,0 +1,100 @@
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,5 +1,3 @@
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,7 +29,6 @@ internal class PluginRepository : IPluginRepository
public void SavePlugin(PluginEntity pluginEntity)
{
_repository.Upsert(pluginEntity);
_repository.Database.Checkpoint();
}
public void AddSetting(PluginSettingEntity pluginSettingEntity)
@ -37,12 +36,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.Interfaces;
using Artemis.Storage.Migrations;
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" Version="11.0.9" />
<PackageReference Include="Avalonia" />
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<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" />
<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" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Artemis.Core\Artemis.Core.csproj" />

View File

@ -18,16 +18,20 @@ 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)
public DataModelUIService(IDataModelService dataModelService, IContainer container, ISettingsService settingsService)
{
_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)
@ -43,7 +47,9 @@ 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,4 +104,14 @@ 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,6 +27,7 @@ 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,6 +29,7 @@ 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,6 +87,7 @@ 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

@ -1,11 +0,0 @@
<?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,13 +21,12 @@
</None>
</ItemGroup>
<ItemGroup>
<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" />
<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" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Artemis.Core\Artemis.Core.csproj" />

View File

@ -10,26 +10,25 @@
<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" 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">
<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">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Splat.DryIoc" Version="14.8.12" />
<PackageReference Include="TextMateSharp.Grammars" Version="1.0.56" />
<PackageReference Include="Splat.DryIoc" />
<PackageReference Include="TextMateSharp.Grammars" />
</ItemGroup>
<ItemGroup>

View File

@ -10,10 +10,8 @@ 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;
@ -49,7 +47,6 @@ public static class ArtemisBootstrapper
_container.RegisterSharedUI();
_container.RegisterUpdatingClient();
_container.RegisterWorkshopClient();
_container.RegisterNoStringEvaluating();
configureServices?.Invoke(_container);
_container.UseDryIocDependencyResolver();

View File

@ -18,11 +18,13 @@ 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;
@ -39,8 +41,9 @@ public partial class NodeViewModel : ActivatableViewModelBase
[Notify] private bool _displayCustomViewModelBelow;
[Notify] private VerticalAlignment _customViewModelVerticalAlignment;
public NodeViewModel(NodeScriptViewModel nodeScriptViewModel, INode node, INodeVmFactory nodeVmFactory, INodeEditorService nodeEditorService, IWindowService windowService)
public NodeViewModel(NodeScriptViewModel nodeScriptViewModel, INode node, ILogger logger, INodeVmFactory nodeVmFactory, INodeEditorService nodeEditorService, IWindowService windowService)
{
_logger = logger;
_nodeEditorService = nodeEditorService;
_windowService = windowService;
NodeScriptViewModel = nodeScriptViewModel;
@ -137,25 +140,7 @@ public partial class NodeViewModel : ActivatableViewModelBase
});
// Set up the custom node VM if needed
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;
}
}
SetupCustomNodeViewModel();
});
}
@ -209,4 +194,35 @@ 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,6 +93,14 @@ 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,5 +6,4 @@ public interface IRegistrationService : IArtemisUIService
void RegisterBuiltInDataModelInputs();
void RegisterBuiltInPropertyEditors();
void RegisterControllers();
void RegisterBuiltInNodeTypes();
}

View File

@ -1,7 +1,4 @@
using System;
using System.Collections;
using System.Linq;
using System.Reflection;
using System.Linq;
using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Controllers;
@ -13,10 +10,8 @@ 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;
@ -26,7 +21,6 @@ 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;
@ -36,7 +30,6 @@ 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
@ -46,13 +39,11 @@ public class RegistrationService : IRegistrationService
_router = router;
_inputService = inputService;
_propertyInputService = propertyInputService;
_nodeService = nodeService;
_dataModelUIService = dataModelUIService;
_webServerService = webServerService;
CreateCursorResources();
RegisterRoutes();
RegisterBuiltInNodeTypes();
RegisterControllers();
}
@ -105,22 +96,4 @@ 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

@ -1,17 +0,0 @@
<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

@ -1,25 +0,0 @@
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

@ -1,27 +0,0 @@
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

@ -1,41 +0,0 @@
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

@ -1,59 +0,0 @@
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