diff --git a/.github/workflows/docfx.yml b/.github/workflows/docfx.yml
index af0986a83..0f3e998b3 100644
--- a/.github/workflows/docfx.yml
+++ b/.github/workflows/docfx.yml
@@ -26,7 +26,7 @@ jobs:
- name: Build DocFX
run: docfx docfx/docfx_project/docfx.json
- name: Upload to FTP
- uses: SamKirkland/FTP-Deploy-Action@4.3.4
+ uses: SamKirkland/FTP-Deploy-Action@v4.3.5
with:
server: www360.your-server.de
protocol: ftps
diff --git a/src/Artemis.Core/Models/BreakableModel.cs b/src/Artemis.Core/Models/BreakableModel.cs
index f0338cd0f..9f2c60ed9 100644
--- a/src/Artemis.Core/Models/BreakableModel.cs
+++ b/src/Artemis.Core/Models/BreakableModel.cs
@@ -61,6 +61,9 @@ public abstract class BreakableModel : CorePropertyChanged, IBreakableModel
///
public void SetBrokenState(string state, Exception? exception = null)
{
+ if (state == BrokenState && BrokenStateException?.StackTrace == exception?.StackTrace)
+ return;
+
BrokenState = state ?? throw new ArgumentNullException(nameof(state));
BrokenStateException = exception;
OnBrokenStateChanged();
diff --git a/src/Artemis.Core/Models/IPluginFeatureDependent.cs b/src/Artemis.Core/Models/IPluginFeatureDependent.cs
new file mode 100644
index 000000000..a1531bfc5
--- /dev/null
+++ b/src/Artemis.Core/Models/IPluginFeatureDependent.cs
@@ -0,0 +1,15 @@
+using System.Collections.Generic;
+
+namespace Artemis.Core;
+
+///
+/// Represents a class that depends on plugin features
+///
+public interface IPluginFeatureDependent
+{
+ ///
+ /// Gets the plugin features this class depends on, may contain the same plugin feature twice if depending on it in multiple ways.
+ ///
+ /// A of this class depends on.
+ public IEnumerable GetFeatureDependencies();
+}
\ No newline at end of file
diff --git a/src/Artemis.Core/Models/Profile/Conditions/AlwaysOnCondition.cs b/src/Artemis.Core/Models/Profile/Conditions/AlwaysOnCondition.cs
index eeeece9c5..b42c1e1fb 100644
--- a/src/Artemis.Core/Models/Profile/Conditions/AlwaysOnCondition.cs
+++ b/src/Artemis.Core/Models/Profile/Conditions/AlwaysOnCondition.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using Artemis.Storage.Entities.Profile.Abstract;
using Artemis.Storage.Entities.Profile.Conditions;
@@ -82,4 +83,14 @@ public class AlwaysOnCondition : ICondition
}
#endregion
+
+ #region Implementation of IPluginFeatureDependent
+
+ ///
+ public IEnumerable GetFeatureDependencies()
+ {
+ return [];
+ }
+
+ #endregion
}
\ No newline at end of file
diff --git a/src/Artemis.Core/Models/Profile/Conditions/EventCondition.cs b/src/Artemis.Core/Models/Profile/Conditions/EventCondition.cs
index f198b5397..826634d67 100644
--- a/src/Artemis.Core/Models/Profile/Conditions/EventCondition.cs
+++ b/src/Artemis.Core/Models/Profile/Conditions/EventCondition.cs
@@ -325,4 +325,14 @@ public class EventCondition : CorePropertyChanged, INodeScriptCondition
}
#endregion
+
+ #region Implementation of IPluginFeatureDependent
+
+ ///
+ public IEnumerable GetFeatureDependencies()
+ {
+ return Script.GetFeatureDependencies().Concat(EventPath?.GetFeatureDependencies() ?? []);
+ }
+
+ #endregion
}
\ No newline at end of file
diff --git a/src/Artemis.Core/Models/Profile/Conditions/ICondition.cs b/src/Artemis.Core/Models/Profile/Conditions/ICondition.cs
index ef3b17c13..b1d3fa9f4 100644
--- a/src/Artemis.Core/Models/Profile/Conditions/ICondition.cs
+++ b/src/Artemis.Core/Models/Profile/Conditions/ICondition.cs
@@ -6,7 +6,7 @@ namespace Artemis.Core;
///
/// Represents a condition applied to a
///
-public interface ICondition : IDisposable, IStorageModel
+public interface ICondition : IDisposable, IStorageModel, IPluginFeatureDependent
{
///
/// Gets the entity used to store this condition
diff --git a/src/Artemis.Core/Models/Profile/Conditions/PlayOnceCondition.cs b/src/Artemis.Core/Models/Profile/Conditions/PlayOnceCondition.cs
index 40178ad6e..4e3ff0ff1 100644
--- a/src/Artemis.Core/Models/Profile/Conditions/PlayOnceCondition.cs
+++ b/src/Artemis.Core/Models/Profile/Conditions/PlayOnceCondition.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using Artemis.Storage.Entities.Profile.Abstract;
using Artemis.Storage.Entities.Profile.Conditions;
@@ -82,4 +83,14 @@ public class PlayOnceCondition : ICondition
}
#endregion
+
+ #region Implementation of IPluginFeatureDependent
+
+ ///
+ public IEnumerable GetFeatureDependencies()
+ {
+ return [];
+ }
+
+ #endregion
}
\ No newline at end of file
diff --git a/src/Artemis.Core/Models/Profile/Conditions/StaticCondition.cs b/src/Artemis.Core/Models/Profile/Conditions/StaticCondition.cs
index 5ab81e22c..fddc62306 100644
--- a/src/Artemis.Core/Models/Profile/Conditions/StaticCondition.cs
+++ b/src/Artemis.Core/Models/Profile/Conditions/StaticCondition.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.Linq;
using Artemis.Storage.Entities.Profile.Abstract;
using Artemis.Storage.Entities.Profile.Conditions;
@@ -159,6 +160,16 @@ public class StaticCondition : CorePropertyChanged, INodeScriptCondition
}
#endregion
+
+ #region Implementation of IPluginFeatureDependent
+
+ ///
+ public IEnumerable GetFeatureDependencies()
+ {
+ return Script.GetFeatureDependencies();
+ }
+
+ #endregion
}
///
diff --git a/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs b/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs
index 42b10ccde..9bf8e94e6 100644
--- a/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs
+++ b/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs
@@ -243,4 +243,14 @@ public class DataBinding : IDataBinding
}
#endregion
+
+ #region Implementation of IPluginFeatureDependent
+
+ ///
+ public IEnumerable GetFeatureDependencies()
+ {
+ return Script.GetFeatureDependencies();
+ }
+
+ #endregion
}
\ No newline at end of file
diff --git a/src/Artemis.Core/Models/Profile/DataBindings/IDataBinding.cs b/src/Artemis.Core/Models/Profile/DataBindings/IDataBinding.cs
index a5856d018..e87868d52 100644
--- a/src/Artemis.Core/Models/Profile/DataBindings/IDataBinding.cs
+++ b/src/Artemis.Core/Models/Profile/DataBindings/IDataBinding.cs
@@ -8,7 +8,7 @@ namespace Artemis.Core;
/// Represents a data binding that binds a certain to a value inside a
///
///
-public interface IDataBinding : IStorageModel, IDisposable
+public interface IDataBinding : IStorageModel, IDisposable, IPluginFeatureDependent
{
///
/// Gets the layer property the data binding is applied to
diff --git a/src/Artemis.Core/Models/Profile/DataModel/DataModelPath.cs b/src/Artemis.Core/Models/Profile/DataModel/DataModelPath.cs
index 681ddb923..deeff25bc 100644
--- a/src/Artemis.Core/Models/Profile/DataModel/DataModelPath.cs
+++ b/src/Artemis.Core/Models/Profile/DataModel/DataModelPath.cs
@@ -11,7 +11,7 @@ namespace Artemis.Core;
///
/// Represents a path that points to a property in data model
///
-public class DataModelPath : IStorageModel, IDisposable
+public class DataModelPath : IStorageModel, IDisposable, IPluginFeatureDependent
{
private readonly LinkedList _segments;
private Expression>? _accessorLambda;
@@ -188,6 +188,14 @@ public class DataModelPath : IStorageModel, IDisposable
return string.IsNullOrWhiteSpace(Path) ? "this" : Path;
}
+ ///
+ public IEnumerable GetFeatureDependencies()
+ {
+ if (Target == null)
+ return [];
+ return [Target.Module];
+ }
+
///
/// Occurs whenever the path becomes invalid
///
diff --git a/src/Artemis.Core/Models/Profile/Folder.cs b/src/Artemis.Core/Models/Profile/Folder.cs
index 2db77c92b..a845bd331 100644
--- a/src/Artemis.Core/Models/Profile/Folder.cs
+++ b/src/Artemis.Core/Models/Profile/Folder.cs
@@ -179,6 +179,14 @@ public sealed class Folder : RenderProfileElement
return $"[Folder] {nameof(Name)}: {Name}, {nameof(Order)}: {Order}";
}
+ ///
+ public override IEnumerable GetFeatureDependencies()
+ {
+ return LayerEffects.SelectMany(e => e.GetFeatureDependencies())
+ .Concat(Children.SelectMany(c => c.GetFeatureDependencies()))
+ .Concat(DisplayCondition.GetFeatureDependencies());
+ }
+
#region Rendering
///
diff --git a/src/Artemis.Core/Models/Profile/Layer.cs b/src/Artemis.Core/Models/Profile/Layer.cs
index 66df68a05..2f7de1d48 100644
--- a/src/Artemis.Core/Models/Profile/Layer.cs
+++ b/src/Artemis.Core/Models/Profile/Layer.cs
@@ -160,10 +160,12 @@ public sealed class Layer : RenderProfileElement
public LayerAdapter Adapter { get; }
///
- public override bool ShouldBeEnabled => !Suspended && DisplayConditionMet;
+ public override bool ShouldBeEnabled => !Suspended && DisplayConditionMet && HasBounds;
internal override RenderElementEntity RenderElementEntity => LayerEntity;
+ private bool HasBounds => Bounds.Width > 0 && Bounds.Height > 0;
+
///
public override List GetAllLayerProperties()
{
@@ -187,6 +189,16 @@ public sealed class Layer : RenderProfileElement
return $"[Layer] {nameof(Name)}: {Name}, {nameof(Order)}: {Order}";
}
+ ///
+ public override IEnumerable GetFeatureDependencies()
+ {
+ return LayerEffects.SelectMany(e => e.GetFeatureDependencies())
+ .Concat(LayerBrush?.GetFeatureDependencies() ?? [])
+ .Concat(General.GetFeatureDependencies())
+ .Concat(Transform.GetFeatureDependencies())
+ .Concat(DisplayCondition.GetFeatureDependencies());
+ }
+
///
/// Occurs when a property affecting the rendering properties of this layer has been updated
///
@@ -383,7 +395,7 @@ public sealed class Layer : RenderProfileElement
if (ShouldBeEnabled)
Enable();
- else if (Suspended || (Timeline.IsFinished && !_renderCopies.Any()))
+ else if (Suspended || !HasBounds || (Timeline.IsFinished && !_renderCopies.Any()))
Disable();
if (!Enabled || Timeline.Delta == TimeSpan.Zero)
@@ -766,7 +778,7 @@ public sealed class Layer : RenderProfileElement
if (!_leds.Remove(led))
return;
-
+
CalculateRenderProperties();
}
diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/FloatRange.cs b/src/Artemis.Core/Models/Profile/LayerProperties/FloatRange.cs
index 5ecc252c0..a926118b2 100644
--- a/src/Artemis.Core/Models/Profile/LayerProperties/FloatRange.cs
+++ b/src/Artemis.Core/Models/Profile/LayerProperties/FloatRange.cs
@@ -1,4 +1,5 @@
using System;
+using System.Text.Json.Serialization;
namespace Artemis.Core;
@@ -14,12 +15,13 @@ public readonly struct FloatRange
///
/// The start value of the range
/// The end value of the range
+ [JsonConstructor]
public FloatRange(float start, float end)
{
Start = start;
End = end;
- _rand = new Random();
+ _rand = Random.Shared;
}
///
diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/ILayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/ILayerProperty.cs
index a7b5aa8ef..e3d1f11e4 100644
--- a/src/Artemis.Core/Models/Profile/LayerProperties/ILayerProperty.cs
+++ b/src/Artemis.Core/Models/Profile/LayerProperties/ILayerProperty.cs
@@ -11,7 +11,7 @@ namespace Artemis.Core;
/// initialize these for you.
///
///
-public interface ILayerProperty : IStorageModel, IDisposable
+public interface ILayerProperty : IStorageModel, IDisposable, IPluginFeatureDependent
{
///
/// Gets the description attribute applied to this property
diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/IntRange.cs b/src/Artemis.Core/Models/Profile/LayerProperties/IntRange.cs
index 3b0c2b5a9..a338290db 100644
--- a/src/Artemis.Core/Models/Profile/LayerProperties/IntRange.cs
+++ b/src/Artemis.Core/Models/Profile/LayerProperties/IntRange.cs
@@ -1,4 +1,5 @@
using System;
+using System.Text.Json.Serialization;
namespace Artemis.Core;
@@ -14,12 +15,13 @@ public readonly struct IntRange
///
/// The start value of the range
/// The end value of the range
+ [JsonConstructor]
public IntRange(int start, int end)
{
Start = start;
End = end;
- _rand = new Random();
+ _rand = Random.Shared;
}
///
diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs
index 5ecfeeca6..85dcba517 100644
--- a/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs
+++ b/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs
@@ -54,6 +54,12 @@ public class LayerProperty : CorePropertyChanged, ILayerProperty
return $"{Path} - {CurrentValue} ({PropertyType})";
}
+ ///
+ public IEnumerable GetFeatureDependencies()
+ {
+ return DataBinding.GetFeatureDependencies();
+ }
+
///
/// Releases the unmanaged resources used by the object and optionally releases the managed resources.
///
diff --git a/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs b/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs
index 31b4af879..84de5c444 100644
--- a/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs
+++ b/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs
@@ -15,7 +15,7 @@ namespace Artemis.Core;
/// initialize these for you.
///
///
-public abstract class LayerPropertyGroup : IDisposable
+public abstract class LayerPropertyGroup : IDisposable, IPluginFeatureDependent
{
private readonly List _layerProperties;
private readonly List _layerPropertyGroups;
@@ -343,4 +343,14 @@ public abstract class LayerPropertyGroup : IDisposable
Dispose(true);
GC.SuppressFinalize(this);
}
+
+ #region Implementation of IPluginFeatureDependent
+
+ ///
+ public IEnumerable GetFeatureDependencies()
+ {
+ return LayerProperties.SelectMany(p => p.GetFeatureDependencies()).Concat(LayerPropertyGroups.SelectMany(g => g.GetFeatureDependencies()));
+ }
+
+ #endregion
}
\ No newline at end of file
diff --git a/src/Artemis.Core/Models/Profile/Profile.cs b/src/Artemis.Core/Models/Profile/Profile.cs
index 2a0d96a23..073afa540 100644
--- a/src/Artemis.Core/Models/Profile/Profile.cs
+++ b/src/Artemis.Core/Models/Profile/Profile.cs
@@ -171,6 +171,12 @@ public sealed class Profile : ProfileElement
return $"[Profile] {nameof(Name)}: {Name}";
}
+ ///
+ public override IEnumerable GetFeatureDependencies()
+ {
+ return GetRootFolder().GetFeatureDependencies().Concat(Scripts.Select(c => c.ScriptingProvider));
+ }
+
///
/// Populates all the LEDs on the elements in this profile
///
diff --git a/src/Artemis.Core/Models/Profile/ProfileElement.cs b/src/Artemis.Core/Models/Profile/ProfileElement.cs
index dbce946a8..921910db5 100644
--- a/src/Artemis.Core/Models/Profile/ProfileElement.cs
+++ b/src/Artemis.Core/Models/Profile/ProfileElement.cs
@@ -9,7 +9,7 @@ namespace Artemis.Core;
///
/// Represents an element of a
///
-public abstract class ProfileElement : BreakableModel, IDisposable
+public abstract class ProfileElement : BreakableModel, IDisposable, IPluginFeatureDependent
{
internal readonly List ChildrenList;
private Guid _entityId;
@@ -122,6 +122,9 @@ public abstract class ProfileElement : BreakableModel, IDisposable
return $"{nameof(EntityId)}: {EntityId}, {nameof(Order)}: {Order}, {nameof(Name)}: {Name}";
}
+ ///
+ public abstract IEnumerable GetFeatureDependencies();
+
///
/// Occurs when a child was added to the list
///
diff --git a/src/Artemis.Core/Models/ProfileConfiguration/ProfileConfiguration.cs b/src/Artemis.Core/Models/ProfileConfiguration/ProfileConfiguration.cs
index 04f84c8f1..a4c1e89f4 100644
--- a/src/Artemis.Core/Models/ProfileConfiguration/ProfileConfiguration.cs
+++ b/src/Artemis.Core/Models/ProfileConfiguration/ProfileConfiguration.cs
@@ -9,13 +9,13 @@ namespace Artemis.Core;
///
/// Represents the configuration of a profile, contained in a
///
-public class ProfileConfiguration : BreakableModel, IStorageModel, IDisposable
+public class ProfileConfiguration : BreakableModel, IStorageModel, IDisposable, IPluginFeatureDependent
{
///
/// Represents an empty profile.
///
public static readonly ProfileConfiguration Empty = new(ProfileCategory.Empty, "Empty", "Empty");
-
+
private ActivationBehaviour _activationBehaviour;
private bool _activationConditionMet;
private ProfileCategory _category;
@@ -146,7 +146,7 @@ public class ProfileConfiguration : BreakableModel, IStorageModel, IDisposable
get => _activationConditionMet;
private set => SetAndNotify(ref _activationConditionMet, value);
}
-
+
///
/// Gets the profile of this profile configuration
///
@@ -159,8 +159,8 @@ public class ProfileConfiguration : BreakableModel, IStorageModel, IDisposable
///
/// Gets or sets a boolean indicating whether this profile should fade in and out when enabling or disabling
///
- public bool FadeInAndOut
- {
+ public bool FadeInAndOut
+ {
get => _fadeInAndOut;
set => SetAndNotify(ref _fadeInAndOut, value);
}
@@ -188,7 +188,7 @@ public class ProfileConfiguration : BreakableModel, IStorageModel, IDisposable
/// alongside any activation requirements of the , if set
///
public NodeScript ActivationCondition { get; }
-
+
///
/// Gets the entity used by this profile config
///
@@ -247,6 +247,19 @@ public class ProfileConfiguration : BreakableModel, IStorageModel, IDisposable
return $"[ProfileConfiguration] {nameof(Name)}: {Name}";
}
+ ///
+ public IEnumerable 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 enabledModules)
{
if (_disposed)
diff --git a/src/Artemis.Core/Plugins/LayerBrushes/Internal/BaseLayerBrush.cs b/src/Artemis.Core/Plugins/LayerBrushes/Internal/BaseLayerBrush.cs
index cadbd5e7e..8410012a9 100644
--- a/src/Artemis.Core/Plugins/LayerBrushes/Internal/BaseLayerBrush.cs
+++ b/src/Artemis.Core/Plugins/LayerBrushes/Internal/BaseLayerBrush.cs
@@ -9,7 +9,7 @@ namespace Artemis.Core.LayerBrushes;
///
/// For internal use only, please use or or instead
///
-public abstract class BaseLayerBrush : BreakableModel, IDisposable
+public abstract class BaseLayerBrush : BreakableModel, IDisposable, IPluginFeatureDependent
{
private LayerBrushType _brushType;
private ILayerBrushConfigurationDialog? _configurationDialog;
@@ -199,6 +199,20 @@ public abstract class BaseLayerBrush : BreakableModel, IDisposable
Dispose(true);
GC.SuppressFinalize(this);
}
+
+ #region Implementation of IPluginFeatureDependent
+
+ ///
+ public IEnumerable GetFeatureDependencies()
+ {
+ IEnumerable result = [Descriptor.Provider];
+ if (BaseProperties != null)
+ result = result.Concat(BaseProperties.GetFeatureDependencies());
+
+ return result;
+ }
+
+ #endregion
}
///
diff --git a/src/Artemis.Core/Plugins/LayerEffects/Internal/BaseLayerEffect.cs b/src/Artemis.Core/Plugins/LayerEffects/Internal/BaseLayerEffect.cs
index 7d300077f..651df32f9 100644
--- a/src/Artemis.Core/Plugins/LayerEffects/Internal/BaseLayerEffect.cs
+++ b/src/Artemis.Core/Plugins/LayerEffects/Internal/BaseLayerEffect.cs
@@ -1,4 +1,6 @@
using System;
+using System.Collections.Generic;
+using System.Linq;
using Artemis.Storage.Entities.Profile;
using SkiaSharp;
@@ -7,7 +9,7 @@ namespace Artemis.Core.LayerEffects;
///
/// For internal use only, please use instead
///
-public abstract class BaseLayerEffect : BreakableModel, IDisposable, IStorageModel
+public abstract class BaseLayerEffect : BreakableModel, IDisposable, IStorageModel, IPluginFeatureDependent
{
private ILayerEffectConfigurationDialog? _configurationDialog;
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
// but LayerEffect outside the core
internal abstract void Initialize();
-
+
internal void InternalUpdate(Timeline timeline)
{
BaseProperties?.Update(timeline);
@@ -235,4 +237,18 @@ public abstract class BaseLayerEffect : BreakableModel, IDisposable, IStorageMod
BaseProperties?.ApplyToEntity();
LayerEffectEntity.PropertyGroup = BaseProperties?.PropertyGroupEntity;
}
+
+ #region Implementation of IPluginFeatureDependent
+
+ ///
+ public IEnumerable GetFeatureDependencies()
+ {
+ IEnumerable result = [Descriptor.Provider];
+ if (BaseProperties != null)
+ result = result.Concat(BaseProperties.GetFeatureDependencies());
+
+ return result;
+ }
+
+ #endregion
}
\ No newline at end of file
diff --git a/src/Artemis.Core/VisualScripting/Interfaces/INode.cs b/src/Artemis.Core/VisualScripting/Interfaces/INode.cs
index 4ce40a0ab..06c382749 100644
--- a/src/Artemis.Core/VisualScripting/Interfaces/INode.cs
+++ b/src/Artemis.Core/VisualScripting/Interfaces/INode.cs
@@ -8,7 +8,7 @@ namespace Artemis.Core;
///
/// Represents a kind of node inside a
///
-public interface INode : INotifyPropertyChanged, IBreakableModel
+public interface INode : INotifyPropertyChanged, IBreakableModel, IPluginFeatureDependent
{
///
/// Gets or sets the ID of the node.
diff --git a/src/Artemis.Core/VisualScripting/Interfaces/INodeScript.cs b/src/Artemis.Core/VisualScripting/Interfaces/INodeScript.cs
index 456692a4d..7ed23e898 100644
--- a/src/Artemis.Core/VisualScripting/Interfaces/INodeScript.cs
+++ b/src/Artemis.Core/VisualScripting/Interfaces/INodeScript.cs
@@ -8,7 +8,7 @@ namespace Artemis.Core;
///
/// Represents a node script
///
-public interface INodeScript : INotifyPropertyChanged, IDisposable, IStorageModel
+public interface INodeScript : INotifyPropertyChanged, IDisposable, IStorageModel, IPluginFeatureDependent
{
///
/// Gets the name of the node script.
diff --git a/src/Artemis.Core/VisualScripting/NodeScript.cs b/src/Artemis.Core/VisualScripting/NodeScript.cs
index e8ccddd4a..8f281f6c3 100644
--- a/src/Artemis.Core/VisualScripting/NodeScript.cs
+++ b/src/Artemis.Core/VisualScripting/NodeScript.cs
@@ -401,6 +401,16 @@ public abstract class NodeScript : CorePropertyChanged, INodeScript
}
#endregion
+
+ #region Implementation of IPluginFeatureDependent
+
+ ///
+ public IEnumerable GetFeatureDependencies()
+ {
+ return Nodes.SelectMany(n => n.GetFeatureDependencies());
+ }
+
+ #endregion
}
///
diff --git a/src/Artemis.Core/VisualScripting/Nodes/Node.cs b/src/Artemis.Core/VisualScripting/Nodes/Node.cs
index 8d4a40d3d..b3c4ad3ee 100644
--- a/src/Artemis.Core/VisualScripting/Nodes/Node.cs
+++ b/src/Artemis.Core/VisualScripting/Nodes/Node.cs
@@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Artemis.Core.Events;
-using DryIoc;
namespace Artemis.Core;
@@ -404,4 +403,14 @@ public abstract class Node : BreakableModel, INode
}
#endregion
+
+ #region Implementation of IPluginFeatureDependent
+
+ ///
+ public virtual IEnumerable GetFeatureDependencies()
+ {
+ return NodeData == null ? [] : [NodeData.Provider];
+ }
+
+ #endregion
}
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/Sidebar/Dialogs/ProfileConfigurationEditViewModel.cs b/src/Artemis.UI/Screens/Sidebar/Dialogs/ProfileConfigurationEditViewModel.cs
index a7ba504ee..1f1c9c500 100644
--- a/src/Artemis.UI/Screens/Sidebar/Dialogs/ProfileConfigurationEditViewModel.cs
+++ b/src/Artemis.UI/Screens/Sidebar/Dialogs/ProfileConfigurationEditViewModel.cs
@@ -162,7 +162,7 @@ public partial class ProfileConfigurationEditViewModel : DialogViewModelBase m.Icon == enumValue)
- : icons.ElementAt(new Random().Next(0, icons.Count - 1));
+ : icons.ElementAt(Random.Shared.Next(0, icons.Count - 1));
}
private async Task SaveIcon()
diff --git a/src/Artemis.UI/Screens/Workshop/Entries/Details/EntryInfoViewModel.cs b/src/Artemis.UI/Screens/Workshop/Entries/Details/EntryInfoViewModel.cs
index 24bb74225..d5c4de433 100644
--- a/src/Artemis.UI/Screens/Workshop/Entries/Details/EntryInfoViewModel.cs
+++ b/src/Artemis.UI/Screens/Workshop/Entries/Details/EntryInfoViewModel.cs
@@ -10,10 +10,10 @@ namespace Artemis.UI.Screens.Workshop.Entries.Details;
public class EntryInfoViewModel : ViewModelBase
{
private readonly INotificationService _notificationService;
- public IGetEntryById_Entry Entry { get; }
+ public IEntryDetails Entry { get; }
public DateTimeOffset? UpdatedAt { get; }
- public EntryInfoViewModel(IGetEntryById_Entry entry, INotificationService notificationService)
+ public EntryInfoViewModel(IEntryDetails entry, INotificationService notificationService)
{
_notificationService = notificationService;
Entry = entry;
diff --git a/src/Artemis.UI/Screens/Workshop/Entries/Details/EntryReleasesViewModel.cs b/src/Artemis.UI/Screens/Workshop/Entries/Details/EntryReleasesViewModel.cs
index 21be13b28..12bd22b3a 100644
--- a/src/Artemis.UI/Screens/Workshop/Entries/Details/EntryReleasesViewModel.cs
+++ b/src/Artemis.UI/Screens/Workshop/Entries/Details/EntryReleasesViewModel.cs
@@ -20,7 +20,7 @@ public class EntryReleasesViewModel : ViewModelBase
private readonly IWindowService _windowService;
private readonly INotificationService _notificationService;
- public EntryReleasesViewModel(IGetEntryById_Entry entry, EntryInstallationHandlerFactory factory, IWindowService windowService, INotificationService notificationService)
+ public EntryReleasesViewModel(IEntryDetails entry, EntryInstallationHandlerFactory factory, IWindowService windowService, INotificationService notificationService)
{
_factory = factory;
_windowService = windowService;
@@ -31,7 +31,7 @@ public class EntryReleasesViewModel : ViewModelBase
OnInstallationStarted = Confirm;
}
- public IGetEntryById_Entry Entry { get; }
+ public IEntryDetails Entry { get; }
public ReactiveCommand DownloadLatestRelease { get; }
public Func> OnInstallationStarted { get; set; }
diff --git a/src/Artemis.UI/Screens/Workshop/Entries/List/EntryListItemView.axaml b/src/Artemis.UI/Screens/Workshop/Entries/List/EntryListItemView.axaml
index f041d5655..91932033a 100644
--- a/src/Artemis.UI/Screens/Workshop/Entries/List/EntryListItemView.axaml
+++ b/src/Artemis.UI/Screens/Workshop/Entries/List/EntryListItemView.axaml
@@ -21,9 +21,11 @@
HorizontalContentAlignment="Stretch"
Command="{CompiledBinding NavigateToEntry}"
IsVisible="{CompiledBinding Entry, Converter={x:Static ObjectConverters.IsNotNull}}">
-
+
-
+
by
@@ -65,7 +67,7 @@
-
+
@@ -73,6 +75,18 @@
downloads
+
+
+
+
+
+ installed
+
+
+
+ update available
+
+
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/Workshop/Entries/List/EntryListItemViewModel.cs b/src/Artemis.UI/Screens/Workshop/Entries/List/EntryListItemViewModel.cs
index 757396c72..d129f9be6 100644
--- a/src/Artemis.UI/Screens/Workshop/Entries/List/EntryListItemViewModel.cs
+++ b/src/Artemis.UI/Screens/Workshop/Entries/List/EntryListItemViewModel.cs
@@ -1,26 +1,39 @@
using System;
using System.Reactive;
+using System.Reactive.Disposables;
using System.Threading.Tasks;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Routing;
using Artemis.WebClient.Workshop;
+using Artemis.WebClient.Workshop.Models;
+using Artemis.WebClient.Workshop.Services;
+using PropertyChanged.SourceGenerator;
using ReactiveUI;
namespace Artemis.UI.Screens.Workshop.Entries.List;
-public class EntryListItemViewModel : ActivatableViewModelBase
+public partial class EntryListItemViewModel : ActivatableViewModelBase
{
private readonly IRouter _router;
+ [Notify] private bool _isInstalled;
+ [Notify] private bool _updateAvailable;
- public EntryListItemViewModel(IGetEntries_Entries_Items entry, IRouter router)
+ public EntryListItemViewModel(IEntrySummary entry, IRouter router, IWorkshopService workshopService)
{
_router = router;
Entry = entry;
NavigateToEntry = ReactiveCommand.CreateFromTask(ExecuteNavigateToEntry);
+
+ this.WhenActivated((CompositeDisposable _) =>
+ {
+ InstalledEntry? installedEntry = workshopService.GetInstalledEntry(entry.Id);
+ IsInstalled = installedEntry != null;
+ UpdateAvailable = installedEntry != null && installedEntry.ReleaseId != entry.LatestReleaseId;
+ });
}
- public IGetEntries_Entries_Items Entry { get; }
+ public IEntrySummary Entry { get; }
public ReactiveCommand NavigateToEntry { get; }
private async Task ExecuteNavigateToEntry()
diff --git a/src/Artemis.UI/Screens/Workshop/Entries/List/EntryListViewModel.cs b/src/Artemis.UI/Screens/Workshop/Entries/List/EntryListViewModel.cs
index 7bbd75704..4d831ba29 100644
--- a/src/Artemis.UI/Screens/Workshop/Entries/List/EntryListViewModel.cs
+++ b/src/Artemis.UI/Screens/Workshop/Entries/List/EntryListViewModel.cs
@@ -21,7 +21,7 @@ namespace Artemis.UI.Screens.Workshop.Entries.List;
public abstract partial class EntryListViewModel : RoutableScreen
{
- private readonly SourceList _entries = new();
+ private readonly SourceList _entries = new();
private readonly ObservableAsPropertyHelper _isLoading;
private readonly INotificationService _notificationService;
private readonly string _route;
@@ -37,13 +37,13 @@ public abstract partial class EntryListViewModel : RoutableScreen getEntryListViewModel)
+ Func getEntryListViewModel)
{
_route = route;
_workshopClient = workshopClient;
_notificationService = notificationService;
- _showPagination = this.WhenAnyValue(vm => vm.TotalPages).Select(t => t > 1).ToProperty(this, vm => vm.ShowPagination);
- _isLoading = this.WhenAnyValue(vm => vm.Page, vm => vm.LoadedPage, (p, c) => p != c).ToProperty(this, vm => vm.IsLoading);
+ _showPagination = this.WhenAnyValue(vm => vm.TotalPages).Select(t => t > 1).ToProperty(this, vm => vm.ShowPagination);
+ _isLoading = this.WhenAnyValue(vm => vm.Page, vm => vm.LoadedPage, (p, c) => p != c).ToProperty(this, vm => vm.IsLoading);
CategoriesViewModel = categoriesViewModel;
InputViewModel = entryListInputViewModel;
diff --git a/src/Artemis.UI/Screens/Workshop/Entries/Tabs/LayoutListViewModel.cs b/src/Artemis.UI/Screens/Workshop/Entries/Tabs/LayoutListViewModel.cs
index ed2fa328b..ca0cdbb99 100644
--- a/src/Artemis.UI/Screens/Workshop/Entries/Tabs/LayoutListViewModel.cs
+++ b/src/Artemis.UI/Screens/Workshop/Entries/Tabs/LayoutListViewModel.cs
@@ -14,7 +14,7 @@ public class LayoutListViewModel : List.EntryListViewModel
CategoriesViewModel categoriesViewModel,
EntryListInputViewModel entryListInputViewModel,
INotificationService notificationService,
- Func getEntryListViewModel)
+ Func getEntryListViewModel)
: base("workshop/entries/layouts", workshopClient, router, categoriesViewModel, entryListInputViewModel, notificationService, getEntryListViewModel)
{
entryListInputViewModel.SearchWatermark = "Search layouts";
diff --git a/src/Artemis.UI/Screens/Workshop/Entries/Tabs/PluginListViewModel.cs b/src/Artemis.UI/Screens/Workshop/Entries/Tabs/PluginListViewModel.cs
index 206697490..7af13b4e4 100644
--- a/src/Artemis.UI/Screens/Workshop/Entries/Tabs/PluginListViewModel.cs
+++ b/src/Artemis.UI/Screens/Workshop/Entries/Tabs/PluginListViewModel.cs
@@ -14,7 +14,7 @@ public class PluginListViewModel : EntryListViewModel
CategoriesViewModel categoriesViewModel,
EntryListInputViewModel entryListInputViewModel,
INotificationService notificationService,
- Func getEntryListViewModel)
+ Func getEntryListViewModel)
: base("workshop/entries/plugins", workshopClient, router, categoriesViewModel, entryListInputViewModel, notificationService, getEntryListViewModel)
{
entryListInputViewModel.SearchWatermark = "Search plugins";
diff --git a/src/Artemis.UI/Screens/Workshop/Entries/Tabs/ProfileListViewModel.cs b/src/Artemis.UI/Screens/Workshop/Entries/Tabs/ProfileListViewModel.cs
index f251ee10d..d8ae07a17 100644
--- a/src/Artemis.UI/Screens/Workshop/Entries/Tabs/ProfileListViewModel.cs
+++ b/src/Artemis.UI/Screens/Workshop/Entries/Tabs/ProfileListViewModel.cs
@@ -14,7 +14,7 @@ public class ProfileListViewModel : List.EntryListViewModel
CategoriesViewModel categoriesViewModel,
EntryListInputViewModel entryListInputViewModel,
INotificationService notificationService,
- Func getEntryListViewModel)
+ Func getEntryListViewModel)
: base("workshop/entries/profiles", workshopClient, router, categoriesViewModel, entryListInputViewModel, notificationService, getEntryListViewModel)
{
entryListInputViewModel.SearchWatermark = "Search profiles";
diff --git a/src/Artemis.UI/Screens/Workshop/Layout/LayoutDetailsView.axaml b/src/Artemis.UI/Screens/Workshop/Layout/LayoutDetailsView.axaml
index cc566cbd3..3dd205871 100644
--- a/src/Artemis.UI/Screens/Workshop/Layout/LayoutDetailsView.axaml
+++ b/src/Artemis.UI/Screens/Workshop/Layout/LayoutDetailsView.axaml
@@ -17,13 +17,17 @@
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Artemis.UI/Screens/Workshop/Layout/LayoutDetailsViewModel.cs b/src/Artemis.UI/Screens/Workshop/Layout/LayoutDetailsViewModel.cs
index 1c12e2dbf..9663f5ad4 100644
--- a/src/Artemis.UI/Screens/Workshop/Layout/LayoutDetailsViewModel.cs
+++ b/src/Artemis.UI/Screens/Workshop/Layout/LayoutDetailsViewModel.cs
@@ -24,10 +24,10 @@ public partial class LayoutDetailsViewModel : RoutableScreen _getEntryInfoViewModel;
- private readonly Func _getEntryReleasesViewModel;
- private readonly Func _getEntryImagesViewModel;
- [Notify] private IGetEntryById_Entry? _entry;
+ private readonly Func _getEntryInfoViewModel;
+ private readonly Func _getEntryReleasesViewModel;
+ private readonly Func _getEntryImagesViewModel;
+ [Notify] private IEntryDetails? _entry;
[Notify] private EntryInfoViewModel? _entryInfoViewModel;
[Notify] private EntryReleasesViewModel? _entryReleasesViewModel;
[Notify] private EntryImagesViewModel? _entryImagesViewModel;
@@ -35,9 +35,9 @@ public partial class LayoutDetailsViewModel : RoutableScreen getEntryInfoViewModel,
- Func getEntryReleasesViewModel,
- Func getEntryImagesViewModel)
+ Func getEntryInfoViewModel,
+ Func getEntryReleasesViewModel,
+ Func getEntryImagesViewModel)
{
_client = client;
_deviceService = deviceService;
@@ -59,19 +59,12 @@ public partial class LayoutDetailsViewModel : RoutableScreen
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+ Used by these profiles
+
+
+
+
+
+
+
+
diff --git a/src/Artemis.UI/Screens/Workshop/Plugins/PluginDetailsViewModel.cs b/src/Artemis.UI/Screens/Workshop/Plugins/PluginDetailsViewModel.cs
index bfce68a94..be800a735 100644
--- a/src/Artemis.UI/Screens/Workshop/Plugins/PluginDetailsViewModel.cs
+++ b/src/Artemis.UI/Screens/Workshop/Plugins/PluginDetailsViewModel.cs
@@ -1,10 +1,13 @@
using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Screens.Workshop.Entries.Details;
+using Artemis.UI.Screens.Workshop.Entries.List;
using Artemis.UI.Screens.Workshop.Parameters;
using Artemis.UI.Screens.Workshop.Plugins.Dialogs;
using Artemis.UI.Shared.Routing;
@@ -21,20 +24,23 @@ public partial class PluginDetailsViewModel : RoutableScreen _getEntryInfoViewModel;
- private readonly Func _getEntryReleasesViewModel;
- private readonly Func _getEntryImagesViewModel;
- [Notify] private IGetEntryById_Entry? _entry;
+ private readonly Func _getEntryInfoViewModel;
+ private readonly Func _getEntryReleasesViewModel;
+ private readonly Func _getEntryImagesViewModel;
+ private readonly Func _getEntryListViewModel;
+ [Notify] private IEntryDetails? _entry;
[Notify] private EntryInfoViewModel? _entryInfoViewModel;
[Notify] private EntryReleasesViewModel? _entryReleasesViewModel;
[Notify] private EntryImagesViewModel? _entryImagesViewModel;
-
+ [Notify] private ReadOnlyObservableCollection? _dependants;
+
public PluginDetailsViewModel(IWorkshopClient client,
IWindowService windowService,
IPluginManagementService pluginManagementService,
- Func getEntryInfoViewModel,
- Func getEntryReleasesViewModel,
- Func getEntryImagesViewModel)
+ Func getEntryInfoViewModel,
+ Func getEntryReleasesViewModel,
+ Func getEntryImagesViewModel,
+ Func getEntryListViewModel)
{
_client = client;
_windowService = windowService;
@@ -42,6 +48,7 @@ public partial class PluginDetailsViewModel : RoutableScreen? dependants = (await _client.GetDependantEntries.ExecuteAsync(entryId, 0, 25, cancellationToken)).Data?.Entries?.Items;
+ Dependants = dependants != null && dependants.Any()
+ ? new ReadOnlyObservableCollection(new ObservableCollection(dependants.Select(_getEntryListViewModel)))
+ : null;
}
private async Task OnInstallationStarted(IEntryDetails entryDetails)
diff --git a/src/Artemis.UI/Screens/Workshop/Profile/ProfileDetailsView.axaml b/src/Artemis.UI/Screens/Workshop/Profile/ProfileDetailsView.axaml
index 0569ee176..499a5b978 100644
--- a/src/Artemis.UI/Screens/Workshop/Profile/ProfileDetailsView.axaml
+++ b/src/Artemis.UI/Screens/Workshop/Profile/ProfileDetailsView.axaml
@@ -17,13 +17,28 @@
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+ Required plugins
+
+
+
+
+
+
+
+
+
diff --git a/src/Artemis.UI/Screens/Workshop/Profile/ProfileDetailsViewModel.cs b/src/Artemis.UI/Screens/Workshop/Profile/ProfileDetailsViewModel.cs
index 6c65b78c4..8aadc53d4 100644
--- a/src/Artemis.UI/Screens/Workshop/Profile/ProfileDetailsViewModel.cs
+++ b/src/Artemis.UI/Screens/Workshop/Profile/ProfileDetailsViewModel.cs
@@ -1,7 +1,11 @@
using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Artemis.UI.Screens.Workshop.Entries.Details;
+using Artemis.UI.Screens.Workshop.Entries.List;
using Artemis.UI.Screens.Workshop.Parameters;
using Artemis.UI.Shared.Routing;
using Artemis.WebClient.Workshop;
@@ -13,23 +17,28 @@ namespace Artemis.UI.Screens.Workshop.Profile;
public partial class ProfileDetailsViewModel : RoutableScreen
{
private readonly IWorkshopClient _client;
- private readonly Func _getEntryInfoViewModel;
- private readonly Func _getEntryReleasesViewModel;
- private readonly Func _getEntryImagesViewModel;
- [Notify] private IGetEntryById_Entry? _entry;
+ private readonly Func _getEntryInfoViewModel;
+ private readonly Func _getEntryReleasesViewModel;
+ private readonly Func _getEntryImagesViewModel;
+ private readonly Func _getEntryListViewModel;
+
+ [Notify] private IEntryDetails? _entry;
[Notify] private EntryInfoViewModel? _entryInfoViewModel;
[Notify] private EntryReleasesViewModel? _entryReleasesViewModel;
[Notify] private EntryImagesViewModel? _entryImagesViewModel;
+ [Notify] private ReadOnlyObservableCollection? _dependencies;
public ProfileDetailsViewModel(IWorkshopClient client,
- Func getEntryInfoViewModel,
- Func getEntryReleasesViewModel,
- Func getEntryImagesViewModel)
+ Func getEntryInfoViewModel,
+ Func getEntryReleasesViewModel,
+ Func getEntryImagesViewModel,
+ Func getEntryListViewModel)
{
_client = client;
_getEntryInfoViewModel = getEntryInfoViewModel;
_getEntryReleasesViewModel = getEntryReleasesViewModel;
_getEntryImagesViewModel = getEntryImagesViewModel;
+ _getEntryListViewModel = getEntryListViewModel;
}
public override async Task OnNavigating(WorkshopDetailParameters parameters, NavigationArguments args, CancellationToken cancellationToken)
@@ -44,16 +53,13 @@ public partial class ProfileDetailsViewModel : RoutableScreen? dependencies = (await _client.GetLatestDependencies.ExecuteAsync(entryId, cancellationToken)).Data?.Entry?.LatestRelease?.Dependencies;
+ Dependencies = dependencies != null && dependencies.Any()
+ ? new ReadOnlyObservableCollection(new ObservableCollection(dependencies.Select(_getEntryListViewModel)))
+ : null;
}
}
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Models/SubmissionWizardState.cs b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Models/SubmissionWizardState.cs
index e06f14ce9..c92f6290b 100644
--- a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Models/SubmissionWizardState.cs
+++ b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Models/SubmissionWizardState.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
+using Artemis.Core;
using Artemis.UI.Screens.Workshop.SubmissionWizard.Steps.Layout;
using Artemis.UI.Screens.Workshop.SubmissionWizard.Steps.Plugin;
using Artemis.UI.Screens.Workshop.SubmissionWizard.Steps.Profile;
@@ -37,7 +38,7 @@ public class SubmissionWizardState : IDisposable
public List Images { get; set; } = new();
public IEntrySource? EntrySource { get; set; }
-
+
public void ChangeScreen() where TSubmissionViewModel : SubmissionViewModel
{
try
diff --git a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/Profile/ProfileSelectionStepViewModel.cs b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/Profile/ProfileSelectionStepViewModel.cs
index d52457a55..4a5b9cae9 100644
--- a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/Profile/ProfileSelectionStepViewModel.cs
+++ b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/Profile/ProfileSelectionStepViewModel.cs
@@ -65,10 +65,10 @@ public partial class ProfileSelectionStepViewModel : SubmissionViewModel
private void ExecuteContinue()
{
- if (SelectedProfile == null)
+ if (SelectedProfile?.Profile == null)
return;
- State.EntrySource = new ProfileEntrySource(SelectedProfile);
+ State.EntrySource = new ProfileEntrySource(SelectedProfile, SelectedProfile.GetFeatureDependencies().Distinct().ToList());
State.Name = SelectedProfile.Name;
State.Icon = SelectedProfile.Icon.GetIconStream();
diff --git a/src/Artemis.WebClient.Workshop/Handlers/InstallationHandlers/Implementations/LayoutEntryInstallationHandler.cs b/src/Artemis.WebClient.Workshop/Handlers/InstallationHandlers/Implementations/LayoutEntryInstallationHandler.cs
index aa755b8a0..3014950f7 100644
--- a/src/Artemis.WebClient.Workshop/Handlers/InstallationHandlers/Implementations/LayoutEntryInstallationHandler.cs
+++ b/src/Artemis.WebClient.Workshop/Handlers/InstallationHandlers/Implementations/LayoutEntryInstallationHandler.cs
@@ -41,7 +41,7 @@ public class LayoutEntryInstallationHandler : IEntryInstallationHandler
}
// Ensure there is an installed entry
- InstalledEntry installedEntry = _workshopService.GetInstalledEntry(entry) ?? new InstalledEntry(entry, release);
+ InstalledEntry installedEntry = _workshopService.GetInstalledEntry(entry.Id) ?? new InstalledEntry(entry, release);
DirectoryInfo releaseDirectory = installedEntry.GetReleaseDirectory(release);
// If the folder already exists, remove it so that if the layout now contains less files, old things dont stick around
diff --git a/src/Artemis.WebClient.Workshop/Handlers/InstallationHandlers/Implementations/PluginEntryInstallationHandler.cs b/src/Artemis.WebClient.Workshop/Handlers/InstallationHandlers/Implementations/PluginEntryInstallationHandler.cs
index 7b9fc5539..34f0aac53 100644
--- a/src/Artemis.WebClient.Workshop/Handlers/InstallationHandlers/Implementations/PluginEntryInstallationHandler.cs
+++ b/src/Artemis.WebClient.Workshop/Handlers/InstallationHandlers/Implementations/PluginEntryInstallationHandler.cs
@@ -25,7 +25,7 @@ public class PluginEntryInstallationHandler : IEntryInstallationHandler
public async Task InstallAsync(IEntryDetails entry, IRelease release, Progress progress, CancellationToken cancellationToken)
{
// Ensure there is an installed entry
- InstalledEntry? installedEntry = _workshopService.GetInstalledEntry(entry);
+ InstalledEntry? installedEntry = _workshopService.GetInstalledEntry(entry.Id);
if (installedEntry != null)
{
// If the folder already exists, we're not going to reinstall the plugin since files may be in use, consider our job done
diff --git a/src/Artemis.WebClient.Workshop/Handlers/InstallationHandlers/Implementations/ProfileEntryInstallationHandler.cs b/src/Artemis.WebClient.Workshop/Handlers/InstallationHandlers/Implementations/ProfileEntryInstallationHandler.cs
index 78c31e98c..218665d05 100644
--- a/src/Artemis.WebClient.Workshop/Handlers/InstallationHandlers/Implementations/ProfileEntryInstallationHandler.cs
+++ b/src/Artemis.WebClient.Workshop/Handlers/InstallationHandlers/Implementations/ProfileEntryInstallationHandler.cs
@@ -36,7 +36,7 @@ public class ProfileEntryInstallationHandler : IEntryInstallationHandler
}
// Find existing installation to potentially replace the profile
- InstalledEntry? installedEntry = _workshopService.GetInstalledEntry(entry);
+ InstalledEntry? installedEntry = _workshopService.GetInstalledEntry(entry.Id);
if (installedEntry != null && installedEntry.TryGetMetadata("ProfileId", out Guid profileId))
{
ProfileConfiguration? existing = _profileService.ProfileCategories.SelectMany(c => c.ProfileConfigurations).FirstOrDefault(c => c.ProfileId == profileId);
diff --git a/src/Artemis.WebClient.Workshop/Handlers/UploadHandlers/Implementations/ProfileEntrySource.cs b/src/Artemis.WebClient.Workshop/Handlers/UploadHandlers/Implementations/ProfileEntrySource.cs
index 739f8e28f..16235a935 100644
--- a/src/Artemis.WebClient.Workshop/Handlers/UploadHandlers/Implementations/ProfileEntrySource.cs
+++ b/src/Artemis.WebClient.Workshop/Handlers/UploadHandlers/Implementations/ProfileEntrySource.cs
@@ -4,10 +4,12 @@ namespace Artemis.WebClient.Workshop.Handlers.UploadHandlers;
public class ProfileEntrySource : IEntrySource
{
- public ProfileEntrySource(ProfileConfiguration profileConfiguration)
+ public ProfileEntrySource(ProfileConfiguration profileConfiguration, List dependencies)
{
ProfileConfiguration = profileConfiguration;
+ Dependencies = dependencies;
}
- public ProfileConfiguration ProfileConfiguration { get; set; }
+ public ProfileConfiguration ProfileConfiguration { get; }
+ public List Dependencies { get; }
}
\ No newline at end of file
diff --git a/src/Artemis.WebClient.Workshop/Handlers/UploadHandlers/Implementations/ProfileEntryUploadHandler.cs b/src/Artemis.WebClient.Workshop/Handlers/UploadHandlers/Implementations/ProfileEntryUploadHandler.cs
index 012491729..3dbbd68e9 100644
--- a/src/Artemis.WebClient.Workshop/Handlers/UploadHandlers/Implementations/ProfileEntryUploadHandler.cs
+++ b/src/Artemis.WebClient.Workshop/Handlers/UploadHandlers/Implementations/ProfileEntryUploadHandler.cs
@@ -31,6 +31,7 @@ public class ProfileEntryUploadHandler : IEntryUploadHandler
MultipartFormDataContent content = new();
StreamContent streamContent = new(archiveStream);
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");
// Submit
diff --git a/src/Artemis.WebClient.Workshop/Models/AccessToken.cs b/src/Artemis.WebClient.Workshop/Models/AccessToken.cs
index 60e9f31d9..d9ed1abb6 100644
--- a/src/Artemis.WebClient.Workshop/Models/AccessToken.cs
+++ b/src/Artemis.WebClient.Workshop/Models/AccessToken.cs
@@ -12,14 +12,16 @@ internal class AuthenticationToken
if (tokenResponse.RefreshToken == null)
throw new ArtemisWebClientException("Token response contains no refresh token");
+ IdentityToken = tokenResponse.IdentityToken;
AccessToken = tokenResponse.AccessToken;
RefreshToken = tokenResponse.RefreshToken;
ExpiresAt = DateTimeOffset.UtcNow.AddSeconds(tokenResponse.ExpiresIn);
}
-
+
public DateTimeOffset ExpiresAt { get; private set; }
public bool Expired => DateTimeOffset.UtcNow.AddSeconds(5) >= ExpiresAt;
+ public string? IdentityToken { get; private set; }
public string AccessToken { get; private set; }
public string RefreshToken { get; private set; }
}
\ No newline at end of file
diff --git a/src/Artemis.WebClient.Workshop/Queries/Fragments.graphql b/src/Artemis.WebClient.Workshop/Queries/Fragments.graphql
index a66c3a660..c89bf34f6 100644
--- a/src/Artemis.WebClient.Workshop/Queries/Fragments.graphql
+++ b/src/Artemis.WebClient.Workshop/Queries/Fragments.graphql
@@ -28,6 +28,20 @@ fragment submittedEntry on Entry {
createdAt
}
+fragment entrySummary on Entry {
+ id
+ author
+ name
+ summary
+ entryType
+ downloads
+ createdAt
+ latestReleaseId
+ categories {
+ ...category
+ }
+}
+
fragment entryDetails on Entry {
id
author
diff --git a/src/Artemis.WebClient.Workshop/Queries/GetDependantEntries.graphql b/src/Artemis.WebClient.Workshop/Queries/GetDependantEntries.graphql
new file mode 100644
index 000000000..551cb140e
--- /dev/null
+++ b/src/Artemis.WebClient.Workshop/Queries/GetDependantEntries.graphql
@@ -0,0 +1,15 @@
+query GetDependantEntries($entryId: Long! $skip: Int $take: Int) {
+ entries(
+ where: {
+ latestRelease: { dependencies: { some: { id: { eq: $entryId } } } }
+ }
+ skip: $skip
+ take: $take
+ order: { createdAt: DESC }
+ ) {
+ totalCount
+ items {
+ ...entrySummary
+ }
+ }
+}
diff --git a/src/Artemis.WebClient.Workshop/Queries/GetEntries.graphql b/src/Artemis.WebClient.Workshop/Queries/GetEntries.graphql
index 366e3bae4..456466a30 100644
--- a/src/Artemis.WebClient.Workshop/Queries/GetEntries.graphql
+++ b/src/Artemis.WebClient.Workshop/Queries/GetEntries.graphql
@@ -2,16 +2,7 @@ query GetEntries($search: String $filter: EntryFilterInput $skip: Int $take: Int
entries(search: $search where: $filter skip: $skip take: $take, order: $order) {
totalCount
items {
- id
- author
- name
- summary
- entryType
- downloads
- createdAt
- categories {
- ...category
- }
+ ...entrySummary
}
}
}
\ No newline at end of file
diff --git a/src/Artemis.WebClient.Workshop/Queries/GetLatestDependencies.graphql b/src/Artemis.WebClient.Workshop/Queries/GetLatestDependencies.graphql
new file mode 100644
index 000000000..67987cde5
--- /dev/null
+++ b/src/Artemis.WebClient.Workshop/Queries/GetLatestDependencies.graphql
@@ -0,0 +1,9 @@
+query GetLatestDependencies($id: Long!) {
+ entry(id: $id) {
+ latestRelease {
+ dependencies {
+ ...entrySummary
+ }
+ }
+ }
+}
diff --git a/src/Artemis.WebClient.Workshop/Services/AuthenticationService.cs b/src/Artemis.WebClient.Workshop/Services/AuthenticationService.cs
index 80a7bb470..c8727c361 100644
--- a/src/Artemis.WebClient.Workshop/Services/AuthenticationService.cs
+++ b/src/Artemis.WebClient.Workshop/Services/AuthenticationService.cs
@@ -258,12 +258,21 @@ internal class AuthenticationService : CorePropertyChanged, IAuthenticationServi
}
///
- public void Logout()
+ public async Task Logout()
{
+ DiscoveryDocumentResponse disco = await GetDiscovery();
+
+ // Open the web browser for the user to log out
+ if (disco.EndSessionEndpoint != null)
+ {
+ RequestUrl authRequestUrl = new(disco.EndSessionEndpoint);
+ string url = authRequestUrl.CreateEndSessionUrl(_token?.IdentityToken);
+ Utilities.OpenUrl(url);
+ }
+
_token = null;
_claims.Clear();
SetStoredRefreshToken(null);
-
_isLoggedInSubject.OnNext(false);
}
diff --git a/src/Artemis.WebClient.Workshop/Services/Interfaces/IAuthenticationService.cs b/src/Artemis.WebClient.Workshop/Services/Interfaces/IAuthenticationService.cs
index b806c83a9..5d8f908b3 100644
--- a/src/Artemis.WebClient.Workshop/Services/Interfaces/IAuthenticationService.cs
+++ b/src/Artemis.WebClient.Workshop/Services/Interfaces/IAuthenticationService.cs
@@ -13,6 +13,6 @@ public interface IAuthenticationService : IProtectedArtemisService
Task GetBearer();
Task AutoLogin(bool force = false);
Task Login(CancellationToken cancellationToken);
- void Logout();
+ Task Logout();
bool GetIsEmailVerified();
}
\ No newline at end of file
diff --git a/src/Artemis.WebClient.Workshop/Services/Interfaces/IWorkshopService.cs b/src/Artemis.WebClient.Workshop/Services/Interfaces/IWorkshopService.cs
index 7bc1a04c1..ea61b3b6d 100644
--- a/src/Artemis.WebClient.Workshop/Services/Interfaces/IWorkshopService.cs
+++ b/src/Artemis.WebClient.Workshop/Services/Interfaces/IWorkshopService.cs
@@ -14,7 +14,7 @@ public interface IWorkshopService
Task NavigateToEntry(long entryId, EntryType entryType);
List GetInstalledEntries();
- InstalledEntry? GetInstalledEntry(IEntryDetails entry);
+ InstalledEntry? GetInstalledEntry(long entryId);
void RemoveInstalledEntry(InstalledEntry installedEntry);
void SaveInstalledEntry(InstalledEntry entry);
void Initialize();
diff --git a/src/Artemis.WebClient.Workshop/Services/WorkshopService.cs b/src/Artemis.WebClient.Workshop/Services/WorkshopService.cs
index 0721c97f0..95b90ccc5 100644
--- a/src/Artemis.WebClient.Workshop/Services/WorkshopService.cs
+++ b/src/Artemis.WebClient.Workshop/Services/WorkshopService.cs
@@ -152,9 +152,9 @@ public class WorkshopService : IWorkshopService
}
///
- public InstalledEntry? GetInstalledEntry(IEntryDetails entry)
+ public InstalledEntry? GetInstalledEntry(long entryId)
{
- EntryEntity? entity = _entryRepository.GetByEntryId(entry.Id);
+ EntryEntity? entity = _entryRepository.GetByEntryId(entryId);
if (entity == null)
return null;
diff --git a/src/Artemis.WebClient.Workshop/graphql.config.yml b/src/Artemis.WebClient.Workshop/graphql.config.yml
index a8ba99703..9662a514f 100644
--- a/src/Artemis.WebClient.Workshop/graphql.config.yml
+++ b/src/Artemis.WebClient.Workshop/graphql.config.yml
@@ -2,7 +2,7 @@ schema: schema.graphql
extensions:
endpoints:
Default GraphQL Endpoint:
- url: https://localhost:7281/graphql
+ url: https://workshop.artemis-rgb.com/graphql
headers:
user-agent: JS GraphQL
introspect: true
diff --git a/src/Artemis.WebClient.Workshop/schema.graphql b/src/Artemis.WebClient.Workshop/schema.graphql
index 959e11204..150e74f3a 100644
--- a/src/Artemis.WebClient.Workshop/schema.graphql
+++ b/src/Artemis.WebClient.Workshop/schema.graphql
@@ -5,8 +5,6 @@ schema {
mutation: Mutation
}
-directive @tag(name: String!) on SCHEMA | SCALAR | OBJECT | FIELD_DEFINITION | ARGUMENT_DEFINITION | INTERFACE | UNION | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION
-
type Category {
icon: String!
id: Long!
@@ -35,6 +33,7 @@ type Entry {
authorId: UUID!
categories: [Category!]!
createdAt: DateTime!
+ dependantReleases: [Release!]!
description: String!
downloads: Long!
entryType: EntryType!
@@ -46,6 +45,7 @@ type Entry {
latestReleaseId: Long
layoutInfo: [LayoutInfo!]!
name: String!
+ pluginInfo: PluginInfo
releases: [Release!]!
summary: String!
tags: [Tag!]!
@@ -84,10 +84,33 @@ type Mutation {
updateEntryImage(input: UpdateEntryImageInput!): Image
}
+type PluginInfo {
+ api: Int
+ entry: Entry!
+ entryId: Long!
+ helpPage: String
+ platforms: PluginPlatform
+ pluginGuid: UUID!
+ repository: String
+ requiresAdmin: Boolean!
+ website: String
+}
+
+"A segment of a collection."
+type PluginInfosCollectionSegment {
+ "A flattened list of the items."
+ items: [PluginInfo!]
+ "Information to aid in pagination."
+ pageInfo: CollectionSegmentInfo!
+ totalCount: Int!
+}
+
type Query {
categories(order: [CategorySortInput!], where: CategoryFilterInput): [Category!]!
entries(order: [EntrySortInput!], search: String, skip: Int, take: Int, where: EntryFilterInput): EntriesCollectionSegment
entry(id: Long!): Entry
+ pluginInfo(pluginGuid: UUID!): PluginInfo
+ pluginInfos(order: [PluginInfoSortInput!], skip: Int, take: Int, where: PluginInfoFilterInput): PluginInfosCollectionSegment
searchEntries(input: String!, order: [EntrySortInput!], type: EntryType, where: EntryFilterInput): [Entry!]!
searchKeyboardLayout(deviceProvider: UUID!, logicalLayout: String, model: String!, physicalLayout: KeyboardLayoutType!, vendor: String!): LayoutInfo
searchLayout(deviceProvider: UUID!, deviceType: RGBDeviceType!, model: String!, vendor: String!): LayoutInfo
@@ -96,6 +119,7 @@ type Query {
type Release {
createdAt: DateTime!
+ dependencies: [Entry!]!
downloadSize: Long!
downloads: Long!
entry: Entry!
@@ -131,11 +155,18 @@ enum KeyboardLayoutType {
UNKNOWN
}
+enum PluginPlatform {
+ LINUX
+ OSX
+ WINDOWS
+}
+
enum RGBDeviceType {
ALL
COOLER
DRAM
FAN
+ GAME_CONTROLLER
GRAPHICS_CARD
HEADSET
HEADSET_STAND
@@ -166,6 +197,11 @@ scalar Long
scalar UUID
+input BooleanOperationFilterInput {
+ eq: Boolean
+ neq: Boolean
+}
+
input CategoryFilterInput {
and: [CategoryFilterInput!]
icon: StringOperationFilterInput
@@ -220,6 +256,7 @@ input EntryFilterInput {
authorId: UuidOperationFilterInput
categories: ListFilterInputTypeOfCategoryFilterInput
createdAt: DateTimeOperationFilterInput
+ dependantReleases: ListFilterInputTypeOfReleaseFilterInput
description: StringOperationFilterInput
downloads: LongOperationFilterInput
entryType: EntryTypeOperationFilterInput
@@ -232,6 +269,7 @@ input EntryFilterInput {
layoutInfo: ListFilterInputTypeOfLayoutInfoFilterInput
name: StringOperationFilterInput
or: [EntryFilterInput!]
+ pluginInfo: PluginInfoFilterInput
releases: ListFilterInputTypeOfReleaseFilterInput
summary: StringOperationFilterInput
tags: ListFilterInputTypeOfTagFilterInput
@@ -250,6 +288,7 @@ input EntrySortInput {
latestRelease: ReleaseSortInput
latestReleaseId: SortEnumType
name: SortEnumType
+ pluginInfo: PluginInfoSortInput
summary: SortEnumType
}
@@ -322,6 +361,13 @@ input ListFilterInputTypeOfCategoryFilterInput {
some: CategoryFilterInput
}
+input ListFilterInputTypeOfEntryFilterInput {
+ all: EntryFilterInput
+ any: Boolean
+ none: EntryFilterInput
+ some: EntryFilterInput
+}
+
input ListFilterInputTypeOfImageFilterInput {
all: ImageFilterInput
any: Boolean
@@ -372,6 +418,39 @@ input NullableOfKeyboardLayoutTypeOperationFilterInput {
nin: [KeyboardLayoutType]
}
+input NullableOfPluginPlatformOperationFilterInput {
+ eq: PluginPlatform
+ in: [PluginPlatform]
+ neq: PluginPlatform
+ nin: [PluginPlatform]
+}
+
+input PluginInfoFilterInput {
+ and: [PluginInfoFilterInput!]
+ api: IntOperationFilterInput
+ entry: EntryFilterInput
+ entryId: LongOperationFilterInput
+ helpPage: StringOperationFilterInput
+ or: [PluginInfoFilterInput!]
+ platforms: NullableOfPluginPlatformOperationFilterInput
+ pluginGuid: UuidOperationFilterInput
+ repository: StringOperationFilterInput
+ requiresAdmin: BooleanOperationFilterInput
+ website: StringOperationFilterInput
+}
+
+input PluginInfoSortInput {
+ api: SortEnumType
+ entry: EntrySortInput
+ entryId: SortEnumType
+ helpPage: SortEnumType
+ platforms: SortEnumType
+ pluginGuid: SortEnumType
+ repository: SortEnumType
+ requiresAdmin: SortEnumType
+ website: SortEnumType
+}
+
input RGBDeviceTypeOperationFilterInput {
eq: RGBDeviceType
in: [RGBDeviceType!]
@@ -382,6 +461,7 @@ input RGBDeviceTypeOperationFilterInput {
input ReleaseFilterInput {
and: [ReleaseFilterInput!]
createdAt: DateTimeOperationFilterInput
+ dependencies: ListFilterInputTypeOfEntryFilterInput
downloadSize: LongOperationFilterInput
downloads: LongOperationFilterInput
entry: EntryFilterInput