Profiles - Added IPluginFeatureDependent interface and implement throughout profiles (#842)

* Profiles - Added IPluginFeatureDependent interface and implement througout profiles
* Workshop - Include dependencies in profile upload request
This commit is contained in:
RobertBeekman authored and GitHub committed 2024-03-03 20:19:36 +01:00
1 parent 28640f9502
commit e5a5f10286
27 files changed
+209 -24

No files matched your search

@@ -0,0 +1,15 @@
using System.Collections.Generic;
namespace Artemis.Core;
/// <summary>
/// Represents a class that depends on plugin features
/// </summary>
public interface IPluginFeatureDependent
{
/// <summary>
/// Gets the plugin features this class depends on, may contain the same plugin feature twice if depending on it in multiple ways.
/// </summary>
/// <returns>A <see cref="List{T}"/> of <see cref="PluginFeature"/> this class depends on.</returns>
public IEnumerable<PluginFeature> GetFeatureDependencies();
}
@@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using Artemis.Storage.Entities.Profile.Abstract; using Artemis.Storage.Entities.Profile.Abstract;
using Artemis.Storage.Entities.Profile.Conditions; using Artemis.Storage.Entities.Profile.Conditions;
@@ -82,4 +83,14 @@ public class AlwaysOnCondition : ICondition
} }
#endregion #endregion
#region Implementation of IPluginFeatureDependent
/// <inheritdoc />
public IEnumerable<PluginFeature> GetFeatureDependencies()
{
return [];
}
#endregion
} }
@@ -325,4 +325,14 @@ public class EventCondition : CorePropertyChanged, INodeScriptCondition
} }
#endregion #endregion
#region Implementation of IPluginFeatureDependent
/// <inheritdoc />
public IEnumerable<PluginFeature> GetFeatureDependencies()
{
return Script.GetFeatureDependencies().Concat(EventPath?.GetFeatureDependencies() ?? []);
}
#endregion
} }
@@ -6,7 +6,7 @@ namespace Artemis.Core;
/// <summary> /// <summary>
/// Represents a condition applied to a <see cref="ProfileElement" /> /// Represents a condition applied to a <see cref="ProfileElement" />
/// </summary> /// </summary>
public interface ICondition : IDisposable, IStorageModel public interface ICondition : IDisposable, IStorageModel, IPluginFeatureDependent
{ {
/// <summary> /// <summary>
/// Gets the entity used to store this condition /// Gets the entity used to store this condition
@@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using Artemis.Storage.Entities.Profile.Abstract; using Artemis.Storage.Entities.Profile.Abstract;
using Artemis.Storage.Entities.Profile.Conditions; using Artemis.Storage.Entities.Profile.Conditions;
@@ -82,4 +83,14 @@ public class PlayOnceCondition : ICondition
} }
#endregion #endregion
#region Implementation of IPluginFeatureDependent
/// <inheritdoc />
public IEnumerable<PluginFeature> GetFeatureDependencies()
{
return [];
}
#endregion
} }
@@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using Artemis.Storage.Entities.Profile.Abstract; using Artemis.Storage.Entities.Profile.Abstract;
using Artemis.Storage.Entities.Profile.Conditions; using Artemis.Storage.Entities.Profile.Conditions;
@@ -159,6 +160,16 @@ public class StaticCondition : CorePropertyChanged, INodeScriptCondition
} }
#endregion #endregion
#region Implementation of IPluginFeatureDependent
/// <inheritdoc />
public IEnumerable<PluginFeature> GetFeatureDependencies()
{
return Script.GetFeatureDependencies();
}
#endregion
} }
/// <summary> /// <summary>
@@ -243,4 +243,14 @@ public class DataBinding<TLayerProperty> : IDataBinding
} }
#endregion #endregion
#region Implementation of IPluginFeatureDependent
/// <inheritdoc />
public IEnumerable<PluginFeature> GetFeatureDependencies()
{
return Script.GetFeatureDependencies();
}
#endregion
} }
@@ -8,7 +8,7 @@ namespace Artemis.Core;
/// Represents a data binding that binds a certain <see cref="LayerProperty{T}" /> to a value inside a /// Represents a data binding that binds a certain <see cref="LayerProperty{T}" /> to a value inside a
/// <see cref="DataModel" /> /// <see cref="DataModel" />
/// </summary> /// </summary>
public interface IDataBinding : IStorageModel, IDisposable public interface IDataBinding : IStorageModel, IDisposable, IPluginFeatureDependent
{ {
/// <summary> /// <summary>
/// Gets the layer property the data binding is applied to /// Gets the layer property the data binding is applied to
@@ -11,7 +11,7 @@ namespace Artemis.Core;
/// <summary> /// <summary>
/// Represents a path that points to a property in data model /// Represents a path that points to a property in data model
/// </summary> /// </summary>
public class DataModelPath : IStorageModel, IDisposable public class DataModelPath : IStorageModel, IDisposable, IPluginFeatureDependent
{ {
private readonly LinkedList<DataModelPathSegment> _segments; private readonly LinkedList<DataModelPathSegment> _segments;
private Expression<Func<object, object>>? _accessorLambda; private Expression<Func<object, object>>? _accessorLambda;
@@ -188,6 +188,14 @@ public class DataModelPath : IStorageModel, IDisposable
return string.IsNullOrWhiteSpace(Path) ? "this" : Path; return string.IsNullOrWhiteSpace(Path) ? "this" : Path;
} }
/// <inheritdoc />
public IEnumerable<PluginFeature> GetFeatureDependencies()
{
if (Target == null)
return [];
return [Target.Module];
}
/// <summary> /// <summary>
/// Occurs whenever the path becomes invalid /// Occurs whenever the path becomes invalid
/// </summary> /// </summary>
@@ -179,6 +179,14 @@ public sealed class Folder : RenderProfileElement
return $"[Folder] {nameof(Name)}: {Name}, {nameof(Order)}: {Order}"; return $"[Folder] {nameof(Name)}: {Name}, {nameof(Order)}: {Order}";
} }
/// <inheritdoc />
public override IEnumerable<PluginFeature> GetFeatureDependencies()
{
return LayerEffects.SelectMany(e => e.GetFeatureDependencies())
.Concat(Children.SelectMany(c => c.GetFeatureDependencies()))
.Concat(DisplayCondition.GetFeatureDependencies());
}
#region Rendering #region Rendering
/// <inheritdoc /> /// <inheritdoc />
+11 -1
View File
@@ -189,6 +189,16 @@ public sealed class Layer : RenderProfileElement
return $"[Layer] {nameof(Name)}: {Name}, {nameof(Order)}: {Order}"; return $"[Layer] {nameof(Name)}: {Name}, {nameof(Order)}: {Order}";
} }
/// <inheritdoc />
public override IEnumerable<PluginFeature> GetFeatureDependencies()
{
return LayerEffects.SelectMany(e => e.GetFeatureDependencies())
.Concat(LayerBrush?.GetFeatureDependencies() ?? [])
.Concat(General.GetFeatureDependencies())
.Concat(Transform.GetFeatureDependencies())
.Concat(DisplayCondition.GetFeatureDependencies());
}
/// <summary> /// <summary>
/// Occurs when a property affecting the rendering properties of this layer has been updated /// Occurs when a property affecting the rendering properties of this layer has been updated
/// </summary> /// </summary>
@@ -768,7 +778,7 @@ public sealed class Layer : RenderProfileElement
if (!_leds.Remove(led)) if (!_leds.Remove(led))
return; return;
CalculateRenderProperties(); CalculateRenderProperties();
} }
@@ -11,7 +11,7 @@ namespace Artemis.Core;
/// initialize these for you. /// initialize these for you.
/// </para> /// </para>
/// </summary> /// </summary>
public interface ILayerProperty : IStorageModel, IDisposable public interface ILayerProperty : IStorageModel, IDisposable, IPluginFeatureDependent
{ {
/// <summary> /// <summary>
/// Gets the description attribute applied to this property /// Gets the description attribute applied to this property
@@ -54,6 +54,12 @@ public class LayerProperty<T> : CorePropertyChanged, ILayerProperty
return $"{Path} - {CurrentValue} ({PropertyType})"; return $"{Path} - {CurrentValue} ({PropertyType})";
} }
/// <inheritdoc />
public IEnumerable<PluginFeature> GetFeatureDependencies()
{
return DataBinding.GetFeatureDependencies();
}
/// <summary> /// <summary>
/// Releases the unmanaged resources used by the object and optionally releases the managed resources. /// Releases the unmanaged resources used by the object and optionally releases the managed resources.
/// </summary> /// </summary>
@@ -15,7 +15,7 @@ namespace Artemis.Core;
/// initialize these for you. /// initialize these for you.
/// </para> /// </para>
/// </summary> /// </summary>
public abstract class LayerPropertyGroup : IDisposable public abstract class LayerPropertyGroup : IDisposable, IPluginFeatureDependent
{ {
private readonly List<ILayerProperty> _layerProperties; private readonly List<ILayerProperty> _layerProperties;
private readonly List<LayerPropertyGroup> _layerPropertyGroups; private readonly List<LayerPropertyGroup> _layerPropertyGroups;
@@ -343,4 +343,14 @@ public abstract class LayerPropertyGroup : IDisposable
Dispose(true); Dispose(true);
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
} }
#region Implementation of IPluginFeatureDependent
/// <inheritdoc />
public IEnumerable<PluginFeature> GetFeatureDependencies()
{
return LayerProperties.SelectMany(p => p.GetFeatureDependencies()).Concat(LayerPropertyGroups.SelectMany(g => g.GetFeatureDependencies()));
}
#endregion
} }
@@ -171,6 +171,12 @@ public sealed class Profile : ProfileElement
return $"[Profile] {nameof(Name)}: {Name}"; return $"[Profile] {nameof(Name)}: {Name}";
} }
/// <inheritdoc />
public override IEnumerable<PluginFeature> GetFeatureDependencies()
{
return GetRootFolder().GetFeatureDependencies().Concat(Scripts.Select(c => c.ScriptingProvider));
}
/// <summary> /// <summary>
/// Populates all the LEDs on the elements in this profile /// Populates all the LEDs on the elements in this profile
/// </summary> /// </summary>
@@ -9,7 +9,7 @@ namespace Artemis.Core;
/// <summary> /// <summary>
/// Represents an element of a <see cref="Profile" /> /// Represents an element of a <see cref="Profile" />
/// </summary> /// </summary>
public abstract class ProfileElement : BreakableModel, IDisposable public abstract class ProfileElement : BreakableModel, IDisposable, IPluginFeatureDependent
{ {
internal readonly List<ProfileElement> ChildrenList; internal readonly List<ProfileElement> ChildrenList;
private Guid _entityId; private Guid _entityId;
@@ -122,6 +122,9 @@ public abstract class ProfileElement : BreakableModel, IDisposable
return $"{nameof(EntityId)}: {EntityId}, {nameof(Order)}: {Order}, {nameof(Name)}: {Name}"; return $"{nameof(EntityId)}: {EntityId}, {nameof(Order)}: {Order}, {nameof(Name)}: {Name}";
} }
/// <inheritdoc />
public abstract IEnumerable<PluginFeature> GetFeatureDependencies();
/// <summary> /// <summary>
/// Occurs when a child was added to the <see cref="Children" /> list /// Occurs when a child was added to the <see cref="Children" /> list
/// </summary> /// </summary>
@@ -9,13 +9,13 @@ namespace Artemis.Core;
/// <summary> /// <summary>
/// Represents the configuration of a profile, contained in a <see cref="ProfileCategory" /> /// Represents the configuration of a profile, contained in a <see cref="ProfileCategory" />
/// </summary> /// </summary>
public class ProfileConfiguration : BreakableModel, IStorageModel, IDisposable public class ProfileConfiguration : BreakableModel, IStorageModel, IDisposable, IPluginFeatureDependent
{ {
/// <summary> /// <summary>
/// Represents an empty profile. /// Represents an empty profile.
/// </summary> /// </summary>
public static readonly ProfileConfiguration Empty = new(ProfileCategory.Empty, "Empty", "Empty"); public static readonly ProfileConfiguration Empty = new(ProfileCategory.Empty, "Empty", "Empty");
private ActivationBehaviour _activationBehaviour; private ActivationBehaviour _activationBehaviour;
private bool _activationConditionMet; private bool _activationConditionMet;
private ProfileCategory _category; private ProfileCategory _category;
@@ -146,7 +146,7 @@ public class ProfileConfiguration : BreakableModel, IStorageModel, IDisposable
get => _activationConditionMet; get => _activationConditionMet;
private set => SetAndNotify(ref _activationConditionMet, value); private set => SetAndNotify(ref _activationConditionMet, value);
} }
/// <summary> /// <summary>
/// Gets the profile of this profile configuration /// Gets the profile of this profile configuration
/// </summary> /// </summary>
@@ -159,8 +159,8 @@ public class ProfileConfiguration : BreakableModel, IStorageModel, IDisposable
/// <summary> /// <summary>
/// Gets or sets a boolean indicating whether this profile should fade in and out when enabling or disabling /// Gets or sets a boolean indicating whether this profile should fade in and out when enabling or disabling
/// </summary> /// </summary>
public bool FadeInAndOut public bool FadeInAndOut
{ {
get => _fadeInAndOut; get => _fadeInAndOut;
set => SetAndNotify(ref _fadeInAndOut, value); set => SetAndNotify(ref _fadeInAndOut, value);
} }
@@ -188,7 +188,7 @@ public class ProfileConfiguration : BreakableModel, IStorageModel, IDisposable
/// alongside any activation requirements of the <see cref="Module" />, if set /// alongside any activation requirements of the <see cref="Module" />, if set
/// </summary> /// </summary>
public NodeScript<bool> ActivationCondition { get; } public NodeScript<bool> ActivationCondition { get; }
/// <summary> /// <summary>
/// Gets the entity used by this profile config /// Gets the entity used by this profile config
/// </summary> /// </summary>
@@ -247,6 +247,19 @@ public class ProfileConfiguration : BreakableModel, IStorageModel, IDisposable
return $"[ProfileConfiguration] {nameof(Name)}: {Name}"; return $"[ProfileConfiguration] {nameof(Name)}: {Name}";
} }
/// <inheritdoc />
public IEnumerable<PluginFeature> GetFeatureDependencies()
{
if (_disposed)
throw new ObjectDisposedException("ProfileConfiguration");
if (Profile == null)
throw new InvalidOperationException("Cannot determine feature dependencies when the profile is not loaded.");
return ActivationCondition.GetFeatureDependencies()
.Concat(Profile.GetFeatureDependencies())
.Concat(Module != null ? [Module] : []);
}
internal void LoadModules(List<Module> enabledModules) internal void LoadModules(List<Module> enabledModules)
{ {
if (_disposed) if (_disposed)
@@ -9,7 +9,7 @@ namespace Artemis.Core.LayerBrushes;
/// <summary> /// <summary>
/// For internal use only, please use <see cref="LayerBrush{T}" /> or <see cref="PerLedLayerBrush{T}" /> or instead /// For internal use only, please use <see cref="LayerBrush{T}" /> or <see cref="PerLedLayerBrush{T}" /> or instead
/// </summary> /// </summary>
public abstract class BaseLayerBrush : BreakableModel, IDisposable public abstract class BaseLayerBrush : BreakableModel, IDisposable, IPluginFeatureDependent
{ {
private LayerBrushType _brushType; private LayerBrushType _brushType;
private ILayerBrushConfigurationDialog? _configurationDialog; private ILayerBrushConfigurationDialog? _configurationDialog;
@@ -199,6 +199,20 @@ public abstract class BaseLayerBrush : BreakableModel, IDisposable
Dispose(true); Dispose(true);
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
} }
#region Implementation of IPluginFeatureDependent
/// <inheritdoc />
public IEnumerable<PluginFeature> GetFeatureDependencies()
{
IEnumerable<PluginFeature> result = [Descriptor.Provider];
if (BaseProperties != null)
result = result.Concat(BaseProperties.GetFeatureDependencies());
return result;
}
#endregion
} }
/// <summary> /// <summary>
@@ -1,4 +1,6 @@
using System; using System;
using System.Collections.Generic;
using System.Linq;
using Artemis.Storage.Entities.Profile; using Artemis.Storage.Entities.Profile;
using SkiaSharp; using SkiaSharp;
@@ -7,7 +9,7 @@ namespace Artemis.Core.LayerEffects;
/// <summary> /// <summary>
/// For internal use only, please use <see cref="LayerEffect{T}" /> instead /// For internal use only, please use <see cref="LayerEffect{T}" /> instead
/// </summary> /// </summary>
public abstract class BaseLayerEffect : BreakableModel, IDisposable, IStorageModel public abstract class BaseLayerEffect : BreakableModel, IDisposable, IStorageModel, IPluginFeatureDependent
{ {
private ILayerEffectConfigurationDialog? _configurationDialog; private ILayerEffectConfigurationDialog? _configurationDialog;
private LayerEffectDescriptor _descriptor; private LayerEffectDescriptor _descriptor;
@@ -164,7 +166,7 @@ public abstract class BaseLayerEffect : BreakableModel, IDisposable, IStorageMod
// Not only is this needed to initialize properties on the layer effects, it also prevents implementing anything // Not only is this needed to initialize properties on the layer effects, it also prevents implementing anything
// but LayerEffect<T> outside the core // but LayerEffect<T> outside the core
internal abstract void Initialize(); internal abstract void Initialize();
internal void InternalUpdate(Timeline timeline) internal void InternalUpdate(Timeline timeline)
{ {
BaseProperties?.Update(timeline); BaseProperties?.Update(timeline);
@@ -235,4 +237,18 @@ public abstract class BaseLayerEffect : BreakableModel, IDisposable, IStorageMod
BaseProperties?.ApplyToEntity(); BaseProperties?.ApplyToEntity();
LayerEffectEntity.PropertyGroup = BaseProperties?.PropertyGroupEntity; LayerEffectEntity.PropertyGroup = BaseProperties?.PropertyGroupEntity;
} }
#region Implementation of IPluginFeatureDependent
/// <inheritdoc />
public IEnumerable<PluginFeature> GetFeatureDependencies()
{
IEnumerable<PluginFeature> result = [Descriptor.Provider];
if (BaseProperties != null)
result = result.Concat(BaseProperties.GetFeatureDependencies());
return result;
}
#endregion
} }
@@ -8,7 +8,7 @@ namespace Artemis.Core;
/// <summary> /// <summary>
/// Represents a kind of node inside a <see cref="INodeScript" /> /// Represents a kind of node inside a <see cref="INodeScript" />
/// </summary> /// </summary>
public interface INode : INotifyPropertyChanged, IBreakableModel public interface INode : INotifyPropertyChanged, IBreakableModel, IPluginFeatureDependent
{ {
/// <summary> /// <summary>
/// Gets or sets the ID of the node. /// Gets or sets the ID of the node.
@@ -8,7 +8,7 @@ namespace Artemis.Core;
/// <summary> /// <summary>
/// Represents a node script /// Represents a node script
/// </summary> /// </summary>
public interface INodeScript : INotifyPropertyChanged, IDisposable, IStorageModel public interface INodeScript : INotifyPropertyChanged, IDisposable, IStorageModel, IPluginFeatureDependent
{ {
/// <summary> /// <summary>
/// Gets the name of the node script. /// Gets the name of the node script.
@@ -401,6 +401,16 @@ public abstract class NodeScript : CorePropertyChanged, INodeScript
} }
#endregion #endregion
#region Implementation of IPluginFeatureDependent
/// <inheritdoc />
public IEnumerable<PluginFeature> GetFeatureDependencies()
{
return Nodes.SelectMany(n => n.GetFeatureDependencies());
}
#endregion
} }
/// <summary> /// <summary>
+10 -1
View File
@@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
using Artemis.Core.Events; using Artemis.Core.Events;
using DryIoc;
namespace Artemis.Core; namespace Artemis.Core;
@@ -404,4 +403,14 @@ public abstract class Node : BreakableModel, INode
} }
#endregion #endregion
#region Implementation of IPluginFeatureDependent
/// <inheritdoc />
public virtual IEnumerable<PluginFeature> GetFeatureDependencies()
{
return NodeData == null ? [] : [NodeData.Provider];
}
#endregion
} }
@@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using Artemis.Core;
using Artemis.UI.Screens.Workshop.SubmissionWizard.Steps.Layout; using Artemis.UI.Screens.Workshop.SubmissionWizard.Steps.Layout;
using Artemis.UI.Screens.Workshop.SubmissionWizard.Steps.Plugin; using Artemis.UI.Screens.Workshop.SubmissionWizard.Steps.Plugin;
using Artemis.UI.Screens.Workshop.SubmissionWizard.Steps.Profile; using Artemis.UI.Screens.Workshop.SubmissionWizard.Steps.Profile;
@@ -37,7 +38,7 @@ public class SubmissionWizardState : IDisposable
public List<ImageUploadRequest> Images { get; set; } = new(); public List<ImageUploadRequest> Images { get; set; } = new();
public IEntrySource? EntrySource { get; set; } public IEntrySource? EntrySource { get; set; }
public void ChangeScreen<TSubmissionViewModel>() where TSubmissionViewModel : SubmissionViewModel public void ChangeScreen<TSubmissionViewModel>() where TSubmissionViewModel : SubmissionViewModel
{ {
try try
@@ -65,10 +65,10 @@ public partial class ProfileSelectionStepViewModel : SubmissionViewModel
private void ExecuteContinue() private void ExecuteContinue()
{ {
if (SelectedProfile == null) if (SelectedProfile?.Profile == null)
return; return;
State.EntrySource = new ProfileEntrySource(SelectedProfile); State.EntrySource = new ProfileEntrySource(SelectedProfile, SelectedProfile.GetFeatureDependencies().Distinct().ToList());
State.Name = SelectedProfile.Name; State.Name = SelectedProfile.Name;
State.Icon = SelectedProfile.Icon.GetIconStream(); State.Icon = SelectedProfile.Icon.GetIconStream();
@@ -4,10 +4,12 @@ namespace Artemis.WebClient.Workshop.Handlers.UploadHandlers;
public class ProfileEntrySource : IEntrySource public class ProfileEntrySource : IEntrySource
{ {
public ProfileEntrySource(ProfileConfiguration profileConfiguration) public ProfileEntrySource(ProfileConfiguration profileConfiguration, List<PluginFeature> dependencies)
{ {
ProfileConfiguration = profileConfiguration; ProfileConfiguration = profileConfiguration;
Dependencies = dependencies;
} }
public ProfileConfiguration ProfileConfiguration { get; set; } public ProfileConfiguration ProfileConfiguration { get; }
public List<PluginFeature> Dependencies { get; }
} }
@@ -31,6 +31,7 @@ public class ProfileEntryUploadHandler : IEntryUploadHandler
MultipartFormDataContent content = new(); MultipartFormDataContent content = new();
StreamContent streamContent = new(archiveStream); StreamContent streamContent = new(archiveStream);
streamContent.Headers.ContentType = new MediaTypeHeaderValue("application/zip"); streamContent.Headers.ContentType = new MediaTypeHeaderValue("application/zip");
content.Add(JsonContent.Create(source.Dependencies.Select(d => new {PluginId = d.Plugin.Guid, FeatureId = d.Id}).ToList()), "ReleaseDependencies");
content.Add(streamContent, "file", "file.zip"); content.Add(streamContent, "file", "file.zip");
// Submit // Submit