diff --git a/src/Artemis.Core/Events/DynamicDataModelEventArgs.cs b/src/Artemis.Core/Events/DynamicDataModelChildEventArgs.cs similarity index 53% rename from src/Artemis.Core/Events/DynamicDataModelEventArgs.cs rename to src/Artemis.Core/Events/DynamicDataModelChildEventArgs.cs index c1b335adc..61e69c6d1 100644 --- a/src/Artemis.Core/Events/DynamicDataModelEventArgs.cs +++ b/src/Artemis.Core/Events/DynamicDataModelChildEventArgs.cs @@ -4,20 +4,20 @@ using Artemis.Core.DataModelExpansions; namespace Artemis.Core { /// - /// Provides data about dynamic data model related events + /// Provides data about dynamic data model child related events /// - public class DynamicDataModelEventArgs : EventArgs + public class DynamicDataModelChildEventArgs : EventArgs { - internal DynamicDataModelEventArgs(DataModel dynamicDataModel, string key) + internal DynamicDataModelChildEventArgs(object? dynamicChild, string key) { - DynamicDataModel = dynamicDataModel; + DynamicChild = dynamicChild; Key = key; } /// - /// Gets the dynamic data model + /// Gets the dynamic data model child /// - public DataModel DynamicDataModel { get; } + public object? DynamicChild { get; } /// /// Gets the key of the dynamic data model on the parent diff --git a/src/Artemis.Core/Models/Profile/DataModel/DataModelPathSegment.cs b/src/Artemis.Core/Models/Profile/DataModel/DataModelPathSegment.cs index c8fd98d79..f78bc7c54 100644 --- a/src/Artemis.Core/Models/Profile/DataModel/DataModelPathSegment.cs +++ b/src/Artemis.Core/Models/Profile/DataModel/DataModelPathSegment.cs @@ -15,6 +15,8 @@ namespace Artemis.Core { private Expression>? _accessorLambda; private DataModel? _dynamicDataModel; + private Type _dynamicDataModelType; + private DataModelPropertyAttribute _dynamicDataModelAttribute; internal DataModelPathSegment(DataModelPath dataModelPath, string identifier, string path) { @@ -49,12 +51,6 @@ namespace Artemis.Core /// public DataModelPathSegmentType Type { get; private set; } - /// - /// Gets the type of dynamic data model this path points to - /// Not used if the is - /// - public Type? DynamicDataModelType { get; private set; } - /// /// Gets the previous segment in the path /// @@ -114,7 +110,7 @@ namespace Artemis.Core { // Dynamic types have a data model description if (Type == DataModelPathSegmentType.Dynamic) - return (GetValue() as DataModel)?.DataModelDescription; + return _dynamicDataModelAttribute; if (IsStartSegment && DataModelPath.Target != null) return DataModelPath.Target.DataModelDescription; if (IsStartSegment) @@ -187,12 +183,12 @@ namespace Artemis.Core return CreateExpression(parameter, expression, nullCondition); // If a dynamic data model is found the use that - bool hasDynamicDataModel = _dynamicDataModel.DynamicDataModels.TryGetValue(Identifier, out DataModel? dynamicDataModel); - if (hasDynamicDataModel && dynamicDataModel != null) - DetermineDynamicType(dynamicDataModel); + bool hasDynamicChild = _dynamicDataModel.DynamicChildren.TryGetValue(Identifier, out DynamicChild? dynamicChild); + if (hasDynamicChild && dynamicChild?.Value != null) + DetermineDynamicType(dynamicChild.Value, dynamicChild.Attribute); - _dynamicDataModel.DynamicDataModelAdded += DynamicDataModelOnDynamicDataModelAdded; - _dynamicDataModel.DynamicDataModelRemoved += DynamicDataModelOnDynamicDataModelRemoved; + _dynamicDataModel.DynamicChildAdded += DynamicChildOnDynamicChildAdded; + _dynamicDataModel.DynamicChildRemoved += DynamicChildOnDynamicChildRemoved; } return CreateExpression(parameter, expression, nullCondition); @@ -219,7 +215,7 @@ namespace Artemis.Core accessorExpression = Expression.Call( expression, nameof(DataModel.DynamicChild), - DynamicDataModelType != null ? new[] {DynamicDataModelType} : null, + _dynamicDataModelType != null ? new[] { _dynamicDataModelType } : null, Expression.Constant(Identifier) ); @@ -236,10 +232,11 @@ namespace Artemis.Core return accessorExpression; } - private void DetermineDynamicType(DataModel dynamicDataModel) + private void DetermineDynamicType(object dynamicDataModel, DataModelPropertyAttribute attribute) { Type = DataModelPathSegmentType.Dynamic; - DynamicDataModelType = dynamicDataModel.GetType(); + _dynamicDataModelType = dynamicDataModel.GetType(); + _dynamicDataModelAttribute = attribute; } private void DetermineStaticType(Type previousType) @@ -263,8 +260,8 @@ namespace Artemis.Core { if (_dynamicDataModel != null) { - _dynamicDataModel.DynamicDataModelAdded -= DynamicDataModelOnDynamicDataModelAdded; - _dynamicDataModel.DynamicDataModelRemoved -= DynamicDataModelOnDynamicDataModelRemoved; + _dynamicDataModel.DynamicChildAdded -= DynamicChildOnDynamicChildAdded; + _dynamicDataModel.DynamicChildRemoved -= DynamicChildOnDynamicChildRemoved; } Type = DataModelPathSegmentType.Invalid; @@ -285,15 +282,15 @@ namespace Artemis.Core #region Event handlers - private void DynamicDataModelOnDynamicDataModelAdded(object? sender, DynamicDataModelEventArgs e) + private void DynamicChildOnDynamicChildAdded(object? sender, DynamicDataModelChildEventArgs e) { if (e.Key == Identifier) DataModelPath.Initialize(); } - private void DynamicDataModelOnDynamicDataModelRemoved(object? sender, DynamicDataModelEventArgs e) + private void DynamicChildOnDynamicChildRemoved(object? sender, DynamicDataModelChildEventArgs e) { - if (e.DynamicDataModel == _dynamicDataModel) + if (e.DynamicChild == _dynamicDataModel) DataModelPath.Initialize(); } diff --git a/src/Artemis.Core/Plugins/DataModelExpansions/DataModel.cs b/src/Artemis.Core/Plugins/DataModelExpansions/DataModel.cs index e207c18eb..392995a4c 100644 --- a/src/Artemis.Core/Plugins/DataModelExpansions/DataModel.cs +++ b/src/Artemis.Core/Plugins/DataModelExpansions/DataModel.cs @@ -14,7 +14,7 @@ namespace Artemis.Core.DataModelExpansions /// public abstract class DataModel { - private readonly Dictionary _dynamicDataModels = new(); + private readonly Dictionary _dynamicChildren = new(); /// /// Creates a new instance of the class @@ -47,10 +47,10 @@ namespace Artemis.Core.DataModelExpansions public bool IsExpansion { get; internal set; } /// - /// Gets an read-only dictionary of all dynamic data models + /// Gets an read-only dictionary of all dynamic children /// [DataModelIgnore] - public ReadOnlyDictionary DynamicDataModels => new(_dynamicDataModels); + public ReadOnlyDictionary DynamicChildren => new(_dynamicChildren); /// /// Returns a read-only collection of all properties in this datamodel that are to be ignored @@ -67,124 +67,211 @@ namespace Artemis.Core.DataModelExpansions } /// - /// Adds a dynamic data model to this data model + /// Adds a dynamic child to this data model /// - /// The dynamic data model to add + /// The dynamic child to add /// The key of the child, must be unique to this data model - /// An optional name, if not provided the key will be used in a humanized form - /// An optional description - public T AddDynamicChild(T dynamicDataModel, string key, string? name = null, string? description = null) where T : DataModel + /// An optional human readable name, if not provided the key will be used in a humanized form + public T AddDynamicChild(T dynamicChild, string key, string? name = null) { - if (dynamicDataModel == null) - throw new ArgumentNullException(nameof(dynamicDataModel)); + if (dynamicChild == null) + throw new ArgumentNullException(nameof(dynamicChild)); if (key == null) throw new ArgumentNullException(nameof(key)); if (key.Contains('.')) throw new ArtemisCoreException("The provided key contains an illegal character (.)"); - if (_dynamicDataModels.ContainsKey(key)) - throw new ArtemisCoreException($"Cannot add a dynamic data model with key '{key}' " + - "because the key is already in use on by another dynamic property this data model."); - - if (_dynamicDataModels.ContainsValue(dynamicDataModel)) + if (_dynamicChildren.ContainsKey(key)) { - string existingKey = _dynamicDataModels.First(kvp => kvp.Value == dynamicDataModel).Key; - throw new ArtemisCoreException($"Cannot add a dynamic data model with key '{key}' " + - $"because the dynamic data model is already added with key '{existingKey}."); + throw new ArtemisCoreException($"Cannot add a dynamic child with key '{key}' " + + "because the key is already in use on by another dynamic property this data model."); } if (GetType().GetProperty(key) != null) - throw new ArtemisCoreException($"Cannot add a dynamic data model with key '{key}' " + + { + throw new ArtemisCoreException($"Cannot add a dynamic child with key '{key}' " + "because the key is already in use by a static property on this data model."); + } - dynamicDataModel.Feature = Feature; - dynamicDataModel.DataModelDescription = new DataModelPropertyAttribute + DataModelPropertyAttribute attribute = new() + { + Name = string.IsNullOrWhiteSpace(name) ? key.Humanize() : name + }; + if (dynamicChild is DataModel dynamicDataModel) + { + dynamicDataModel.Feature = Feature; + dynamicDataModel.DataModelDescription = attribute; + } + + _dynamicChildren.Add(key, new DynamicChild(attribute, dynamicChild)); + + OnDynamicDataModelAdded(new DynamicDataModelChildEventArgs(dynamicChild, key)); + return dynamicChild; + } + + /// + /// Adds a dynamic child to this data model + /// + /// The dynamic child to add + /// The key of the child, must be unique to this data model + /// A human readable for your dynamic child, shown in the UI + /// An optional description, shown in the UI + public T AddDynamicChild(T dynamicChild, string key, string name, string description) + { + if (dynamicChild == null) + throw new ArgumentNullException(nameof(dynamicChild)); + if (key == null) + throw new ArgumentNullException(nameof(key)); + if (key.Contains('.')) + throw new ArtemisCoreException("The provided key contains an illegal character (.)"); + if (_dynamicChildren.ContainsKey(key)) + { + throw new ArtemisCoreException($"Cannot add a dynamic child with key '{key}' " + + "because the key is already in use on by another dynamic property this data model."); + } + + if (GetType().GetProperty(key) != null) + { + throw new ArtemisCoreException($"Cannot add a dynamic child with key '{key}' " + + "because the key is already in use by a static property on this data model."); + } + + DataModelPropertyAttribute attribute = new() { Name = string.IsNullOrWhiteSpace(name) ? key.Humanize() : name, Description = description }; - _dynamicDataModels.Add(key, dynamicDataModel); + if (dynamicChild is DataModel dynamicDataModel) + { + dynamicDataModel.Feature = Feature; + dynamicDataModel.DataModelDescription = attribute; + } - OnDynamicDataModelAdded(new DynamicDataModelEventArgs(dynamicDataModel, key)); - return dynamicDataModel; + _dynamicChildren.Add(key, new DynamicChild(attribute, dynamicChild)); + + OnDynamicDataModelAdded(new DynamicDataModelChildEventArgs(dynamicChild, key)); + return dynamicChild; } /// - /// Removes a dynamic data model from the data model by its key + /// Adds a dynamic child to this data model /// - /// The key of the dynamic data model to remove + /// The dynamic child to add + /// The key of the child, must be unique to this data model + /// A data model property attribute describing the dynamic child + public T AddDynamicChild(T dynamicChild, string key, DataModelPropertyAttribute attribute) + { + if (dynamicChild == null) throw new ArgumentNullException(nameof(dynamicChild)); + if (key == null) throw new ArgumentNullException(nameof(key)); + if (attribute == null) throw new ArgumentNullException(nameof(attribute)); + if (key.Contains('.')) + throw new ArtemisCoreException("The provided key contains an illegal character (.)"); + if (_dynamicChildren.ContainsKey(key)) + { + throw new ArtemisCoreException($"Cannot add a dynamic child with key '{key}' " + + "because the key is already in use on by another dynamic property this data model."); + } + + if (GetType().GetProperty(key) != null) + { + throw new ArtemisCoreException($"Cannot add a dynamic child with key '{key}' " + + "because the key is already in use by a static property on this data model."); + } + + // Make sure a name is on the attribute or funny things might happen + attribute.Name ??= key.Humanize(); + if (dynamicChild is DataModel dynamicDataModel) + { + dynamicDataModel.Feature = Feature; + dynamicDataModel.DataModelDescription = attribute; + } + + _dynamicChildren.Add(key, new DynamicChild(attribute, dynamicChild)); + + OnDynamicDataModelAdded(new DynamicDataModelChildEventArgs(dynamicChild, key)); + return dynamicChild; + } + + /// + /// Removes a dynamic child from the data model by its key + /// + /// The key of the dynamic child to remove public void RemoveDynamicChildByKey(string key) { - _dynamicDataModels.TryGetValue(key, out DataModel? childDataModel); - if (childDataModel == null) + if (!_dynamicChildren.TryGetValue(key, out DynamicChild? dynamicChild)) return; - _dynamicDataModels.Remove(key); - OnDynamicDataModelRemoved(new DynamicDataModelEventArgs(childDataModel, key)); + _dynamicChildren.Remove(key); + OnDynamicDataModelRemoved(new DynamicDataModelChildEventArgs(dynamicChild.Value, key)); } /// - /// Removes a dynamic data model from this data model + /// Removes a dynamic child from this data model /// - /// The dynamic data model to remove - public void RemoveDynamicChild(DataModel dynamicDataModel) + /// The dynamic data child to remove + public void RemoveDynamicChild(T dynamicChild) where T : class { - List keys = _dynamicDataModels.Where(kvp => kvp.Value == dynamicDataModel).Select(kvp => kvp.Key).ToList(); + List keys = _dynamicChildren.Where(kvp => kvp.Value.Value == dynamicChild).Select(kvp => kvp.Key).ToList(); foreach (string key in keys) { - _dynamicDataModels.Remove(key); - OnDynamicDataModelRemoved(new DynamicDataModelEventArgs(dynamicDataModel, key)); + _dynamicChildren.Remove(key); + OnDynamicDataModelRemoved(new DynamicDataModelChildEventArgs(dynamicChild, key)); } } /// - /// Removes all dynamic data models from this data model + /// Removes all dynamic children from this data model /// public void ClearDynamicChildren() { - while (_dynamicDataModels.Any()) - RemoveDynamicChildByKey(_dynamicDataModels.First().Key); + while (_dynamicChildren.Any()) + RemoveDynamicChildByKey(_dynamicChildren.First().Key); } /// - /// Gets a dynamic data model of type by its key + /// Gets a dynamic child of type by its key /// /// The type of data model you expect - /// The unique key of the dynamic data model - /// If found, the dynamic data model otherwise null - public T? DynamicChild(string key) where T : DataModel + /// The unique key of the dynamic child + /// If found, the dynamic child otherwise null + public T? DynamicChild(string key) { - _dynamicDataModels.TryGetValue(key, out DataModel? value); - return value as T; - } + if (!_dynamicChildren.TryGetValue(key, out DynamicChild? dynamicChild)) + return default; - #region Events - - /// - /// Occurs when a dynamic data model has been added to this data model - /// - public event EventHandler? DynamicDataModelAdded; - - /// - /// Occurs when a dynamic data model has been removed from this data model - /// - public event EventHandler? DynamicDataModelRemoved; - - /// - /// Invokes the event - /// - protected virtual void OnDynamicDataModelAdded(DynamicDataModelEventArgs e) - { - DynamicDataModelAdded?.Invoke(this, e); + if (dynamicChild.Value is not T) + return default; + return (T?) dynamicChild.Value; } /// - /// Invokes the event + /// Occurs when a dynamic child has been added to this data model /// - protected virtual void OnDynamicDataModelRemoved(DynamicDataModelEventArgs e) + public event EventHandler? DynamicChildAdded; + + /// + /// Occurs when a dynamic child has been removed from this data model + /// + public event EventHandler? DynamicChildRemoved; + + /// + /// Invokes the event + /// + protected virtual void OnDynamicDataModelAdded(DynamicDataModelChildEventArgs e) { - DynamicDataModelRemoved?.Invoke(this, e); + DynamicChildAdded?.Invoke(this, e); } - #endregion + /// + /// Invokes the event + /// + protected virtual void OnDynamicDataModelRemoved(DynamicDataModelChildEventArgs e) + { + DynamicChildRemoved?.Invoke(this, e); + } } + + /// + /// Represents a record of a dynamic child value with its property attribute + /// + public record DynamicChild(DataModelPropertyAttribute Attribute, object? Value); } \ No newline at end of file diff --git a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelVisualizationViewModel.cs b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelVisualizationViewModel.cs index 56bbbf5e7..78490468a 100644 --- a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelVisualizationViewModel.cs +++ b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelVisualizationViewModel.cs @@ -239,9 +239,10 @@ namespace Artemis.UI.Shared // Add missing dynamic children object? value = Parent == null || Parent.IsRootViewModel ? DataModel : DataModelPath?.GetValue(); if (value is DataModel dataModel) - foreach (KeyValuePair kvp in dataModel.DynamicDataModels) + { + foreach (var (key, dynamicChild) in dataModel.DynamicChildren) { - string childPath = AppendToPath(kvp.Key); + string childPath = AppendToPath(key); if (Children.Any(c => c.Path != null && c.Path.Equals(childPath))) continue; @@ -249,6 +250,7 @@ namespace Artemis.UI.Shared if (child != null) Children.Add(child); } + } // Remove dynamic children that have been removed from the data model List toRemoveDynamic = Children.Where(c => c.DataModelPath != null && !c.DataModelPath.IsValid).ToList();