mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
Display conditions - Seperate list conditions into their own type
This commit is contained in:
parent
f2f77da953
commit
f359256ede
@ -12,19 +12,21 @@ namespace Artemis.Core.Models.Profile.Conditions
|
||||
public DisplayConditionGroup(DisplayConditionPart parent)
|
||||
{
|
||||
Parent = parent;
|
||||
DisplayConditionGroupEntity = new DisplayConditionGroupEntity();
|
||||
Entity = new DisplayConditionGroupEntity();
|
||||
}
|
||||
|
||||
public DisplayConditionGroup(DisplayConditionPart parent, DisplayConditionGroupEntity entity)
|
||||
{
|
||||
Parent = parent;
|
||||
DisplayConditionGroupEntity = entity;
|
||||
BooleanOperator = (BooleanOperator) DisplayConditionGroupEntity.BooleanOperator;
|
||||
Entity = entity;
|
||||
BooleanOperator = (BooleanOperator) Entity.BooleanOperator;
|
||||
|
||||
foreach (var childEntity in DisplayConditionGroupEntity.Children)
|
||||
foreach (var childEntity in Entity.Children)
|
||||
{
|
||||
if (childEntity is DisplayConditionGroupEntity groupEntity)
|
||||
AddChild(new DisplayConditionGroup(this, groupEntity));
|
||||
else if (childEntity is DisplayConditionListEntity listEntity)
|
||||
AddChild(new DisplayConditionList(this, listEntity));
|
||||
else if (childEntity is DisplayConditionPredicateEntity predicateEntity)
|
||||
AddChild(new DisplayConditionPredicate(this, predicateEntity));
|
||||
else if (childEntity is DisplayConditionListPredicateEntity listPredicateEntity)
|
||||
@ -33,7 +35,7 @@ namespace Artemis.Core.Models.Profile.Conditions
|
||||
}
|
||||
|
||||
public BooleanOperator BooleanOperator { get; set; }
|
||||
public DisplayConditionGroupEntity DisplayConditionGroupEntity { get; set; }
|
||||
public DisplayConditionGroupEntity Entity { get; set; }
|
||||
|
||||
public override bool Evaluate()
|
||||
{
|
||||
@ -80,10 +82,10 @@ namespace Artemis.Core.Models.Profile.Conditions
|
||||
|
||||
internal override void ApplyToEntity()
|
||||
{
|
||||
DisplayConditionGroupEntity.BooleanOperator = (int) BooleanOperator;
|
||||
Entity.BooleanOperator = (int) BooleanOperator;
|
||||
|
||||
DisplayConditionGroupEntity.Children.Clear();
|
||||
DisplayConditionGroupEntity.Children.AddRange(Children.Select(c => c.GetEntity()));
|
||||
Entity.Children.Clear();
|
||||
Entity.Children.AddRange(Children.Select(c => c.GetEntity()));
|
||||
foreach (var child in Children)
|
||||
child.ApplyToEntity();
|
||||
}
|
||||
@ -96,7 +98,7 @@ namespace Artemis.Core.Models.Profile.Conditions
|
||||
|
||||
internal override DisplayConditionPartEntity GetEntity()
|
||||
{
|
||||
return DisplayConditionGroupEntity;
|
||||
return Entity;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,148 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using Artemis.Core.Exceptions;
|
||||
using Artemis.Core.Models.Profile.Conditions.Abstract;
|
||||
using Artemis.Core.Plugins.Abstract.DataModels;
|
||||
using Artemis.Core.Services.Interfaces;
|
||||
using Artemis.Storage.Entities.Profile;
|
||||
using Artemis.Storage.Entities.Profile.Abstract;
|
||||
|
||||
namespace Artemis.Core.Models.Profile.Conditions
|
||||
{
|
||||
public class DisplayConditionList : DisplayConditionPart
|
||||
{
|
||||
public DisplayConditionList(DisplayConditionPart parent)
|
||||
{
|
||||
Parent = parent;
|
||||
Entity = new DisplayConditionListEntity();
|
||||
|
||||
// There is always a child root group, add it
|
||||
AddChild(new DisplayConditionGroup(this));
|
||||
}
|
||||
|
||||
public DisplayConditionList(DisplayConditionPart parent, DisplayConditionListEntity entity)
|
||||
{
|
||||
Parent = parent;
|
||||
Entity = entity;
|
||||
ListOperator = (ListOperator) entity.ListOperator;
|
||||
|
||||
// There should only be one child and it should be a group
|
||||
var rootGroup = Entity.Children.SingleOrDefault() as DisplayConditionGroupEntity;
|
||||
if (rootGroup == null)
|
||||
{
|
||||
Entity.Children.Clear();
|
||||
AddChild(new DisplayConditionGroup(this));
|
||||
}
|
||||
else
|
||||
AddChild(new DisplayConditionGroup(this, rootGroup));
|
||||
}
|
||||
|
||||
public DisplayConditionListEntity Entity { get; set; }
|
||||
|
||||
public ListOperator ListOperator { get; set; }
|
||||
public DataModel ListDataModel { get; private set; }
|
||||
public string ListPropertyPath { get; private set; }
|
||||
|
||||
public override bool Evaluate()
|
||||
{
|
||||
if (CompiledListAccessor == null)
|
||||
return false;
|
||||
|
||||
return EvaluateObject(CompiledListAccessor(ListDataModel));
|
||||
}
|
||||
|
||||
public override bool EvaluateObject(object target)
|
||||
{
|
||||
if (!(target is IList list))
|
||||
return false;
|
||||
|
||||
var objectList = list.Cast<object>();
|
||||
return ListOperator switch
|
||||
{
|
||||
ListOperator.Any => objectList.Any(o => Children[0].EvaluateObject(o)),
|
||||
ListOperator.All => objectList.All(o => Children[0].EvaluateObject(o)),
|
||||
ListOperator.None => objectList.Any(o => !Children[0].EvaluateObject(o)),
|
||||
ListOperator.Count => false,
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
}
|
||||
|
||||
internal override void ApplyToEntity()
|
||||
{
|
||||
// Target list
|
||||
Entity.ListDataModelGuid = ListDataModel?.PluginInfo?.Guid;
|
||||
Entity.ListPropertyPath = ListPropertyPath;
|
||||
|
||||
// Operator
|
||||
Entity.ListOperator = (int) ListOperator;
|
||||
|
||||
// Children
|
||||
Entity.Children.Clear();
|
||||
Entity.Children.AddRange(Children.Select(c => c.GetEntity()));
|
||||
foreach (var child in Children)
|
||||
child.ApplyToEntity();
|
||||
}
|
||||
|
||||
internal override DisplayConditionPartEntity GetEntity()
|
||||
{
|
||||
return Entity;
|
||||
}
|
||||
|
||||
internal override void Initialize(IDataModelService dataModelService)
|
||||
{
|
||||
// Target list
|
||||
if (Entity.ListDataModelGuid != null)
|
||||
{
|
||||
var dataModel = dataModelService.GetPluginDataModelByGuid(Entity.ListDataModelGuid.Value);
|
||||
if (dataModel != null && dataModel.ContainsPath(Entity.ListPropertyPath))
|
||||
UpdateList(dataModel, Entity.ListPropertyPath);
|
||||
}
|
||||
|
||||
// Children
|
||||
var rootGroup = (DisplayConditionGroup) Children.Single();
|
||||
rootGroup.Initialize(dataModelService);
|
||||
}
|
||||
|
||||
public void UpdateList(DataModel dataModel, string path)
|
||||
{
|
||||
if (dataModel != null && path == null)
|
||||
throw new ArtemisCoreException("If a data model is provided, a path is also required");
|
||||
if (dataModel == null && path != null)
|
||||
throw new ArtemisCoreException("If path is provided, a data model is also required");
|
||||
|
||||
if (dataModel != null)
|
||||
{
|
||||
if (!dataModel.ContainsPath(path))
|
||||
throw new ArtemisCoreException($"Data model of type {dataModel.GetType().Name} does not contain a property at path '{path}'");
|
||||
if (dataModel.GetListTypeAtPath(path) == null)
|
||||
throw new ArtemisCoreException($"The path '{path}' does not contain a list");
|
||||
}
|
||||
|
||||
ListDataModel = dataModel;
|
||||
ListPropertyPath = path;
|
||||
|
||||
if (dataModel != null)
|
||||
{
|
||||
var parameter = Expression.Parameter(typeof(object), "listDataModel");
|
||||
var accessor = path.Split('.').Aggregate<string, Expression>(
|
||||
Expression.Convert(parameter, dataModel.GetType()),
|
||||
(expression, s) => Expression.Convert(Expression.Property(expression, s), typeof(IList)));
|
||||
|
||||
var lambda = Expression.Lambda<Func<object, IList>>(accessor, parameter);
|
||||
CompiledListAccessor = lambda.Compile();
|
||||
}
|
||||
}
|
||||
|
||||
public Func<object, IList> CompiledListAccessor { get; set; }
|
||||
}
|
||||
|
||||
public enum ListOperator
|
||||
{
|
||||
Any,
|
||||
All,
|
||||
None,
|
||||
Count
|
||||
}
|
||||
}
|
||||
@ -1,106 +1,47 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using Artemis.Core.Exceptions;
|
||||
using Artemis.Core.Extensions;
|
||||
using Artemis.Core.Models.Profile.Conditions.Abstract;
|
||||
using Artemis.Core.Plugins.Abstract.DataModels;
|
||||
using Artemis.Core.Services.Interfaces;
|
||||
using Artemis.Storage.Entities.Profile;
|
||||
using Artemis.Storage.Entities.Profile.Abstract;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Artemis.Core.Models.Profile.Conditions
|
||||
{
|
||||
public class DisplayConditionListPredicate : DisplayConditionPart
|
||||
{
|
||||
public DisplayConditionListPredicate(DisplayConditionPart parent)
|
||||
public DisplayConditionListPredicate(DisplayConditionPart parent, PredicateType predicateType)
|
||||
{
|
||||
Parent = parent;
|
||||
DisplayConditionListPredicateEntity = new DisplayConditionListPredicateEntity();
|
||||
|
||||
// There is always a child root group, add it
|
||||
AddChild(new DisplayConditionGroup(this));
|
||||
PredicateType = predicateType;
|
||||
Entity = new DisplayConditionListPredicateEntity();
|
||||
}
|
||||
|
||||
public DisplayConditionListPredicate(DisplayConditionPart parent, DisplayConditionListPredicateEntity entity)
|
||||
{
|
||||
Parent = parent;
|
||||
DisplayConditionListPredicateEntity = entity;
|
||||
ListOperator = (ListOperator) entity.ListOperator;
|
||||
|
||||
// There should only be one child and it should be a group
|
||||
var rootGroup = DisplayConditionListPredicateEntity.Children.SingleOrDefault() as DisplayConditionGroupEntity;
|
||||
if (rootGroup == null)
|
||||
{
|
||||
DisplayConditionListPredicateEntity.Children.Clear();
|
||||
AddChild(new DisplayConditionGroup(this));
|
||||
}
|
||||
else
|
||||
AddChild(new DisplayConditionGroup(this, rootGroup));
|
||||
Entity = entity;
|
||||
PredicateType = (PredicateType) entity.PredicateType;
|
||||
}
|
||||
|
||||
public DisplayConditionListPredicateEntity DisplayConditionListPredicateEntity { get; set; }
|
||||
public DisplayConditionListPredicateEntity Entity { get; set; }
|
||||
|
||||
public ListOperator ListOperator { get; set; }
|
||||
public PredicateType PredicateType { get; set; }
|
||||
public DisplayConditionOperator Operator { get; private set; }
|
||||
|
||||
public Type ListType { get; private set; }
|
||||
public DataModel ListDataModel { get; private set; }
|
||||
public string ListPropertyPath { get; private set; }
|
||||
|
||||
public override bool Evaluate()
|
||||
{
|
||||
return EvaluateObject(CompiledListAccessor(ListDataModel));
|
||||
}
|
||||
public string LeftPropertyPath { get; private set; }
|
||||
public string RightPropertyPath { get; private set; }
|
||||
public object RightStaticValue { get; private set; }
|
||||
|
||||
public override bool EvaluateObject(object target)
|
||||
{
|
||||
if (!(target is IList list))
|
||||
return false;
|
||||
|
||||
var objectList = list.Cast<object>();
|
||||
return ListOperator switch
|
||||
{
|
||||
ListOperator.Any => objectList.Any(o => Children[0].EvaluateObject(o)),
|
||||
ListOperator.All => objectList.All(o => Children[0].EvaluateObject(o)),
|
||||
ListOperator.None => objectList.Any(o => !Children[0].EvaluateObject(o)),
|
||||
ListOperator.Count => false,
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
}
|
||||
|
||||
internal override void ApplyToEntity()
|
||||
{
|
||||
// Target list
|
||||
DisplayConditionListPredicateEntity.ListDataModelGuid = ListDataModel?.PluginInfo?.Guid;
|
||||
DisplayConditionListPredicateEntity.ListPropertyPath = ListPropertyPath;
|
||||
|
||||
// Operator
|
||||
DisplayConditionListPredicateEntity.ListOperator = (int) ListOperator;
|
||||
|
||||
// Children
|
||||
DisplayConditionListPredicateEntity.Children.Clear();
|
||||
DisplayConditionListPredicateEntity.Children.AddRange(Children.Select(c => c.GetEntity()));
|
||||
foreach (var child in Children)
|
||||
child.ApplyToEntity();
|
||||
}
|
||||
|
||||
internal override DisplayConditionPartEntity GetEntity()
|
||||
{
|
||||
return DisplayConditionListPredicateEntity;
|
||||
}
|
||||
|
||||
internal override void Initialize(IDataModelService dataModelService)
|
||||
{
|
||||
// Target list
|
||||
if (DisplayConditionListPredicateEntity.ListDataModelGuid != null)
|
||||
{
|
||||
var dataModel = dataModelService.GetPluginDataModelByGuid(DisplayConditionListPredicateEntity.ListDataModelGuid.Value);
|
||||
if (dataModel != null && dataModel.ContainsPath(DisplayConditionListPredicateEntity.ListPropertyPath))
|
||||
UpdateList(dataModel, DisplayConditionListPredicateEntity.ListPropertyPath);
|
||||
}
|
||||
|
||||
// Children
|
||||
var rootGroup = (DisplayConditionGroup) Children.Single();
|
||||
rootGroup.Initialize(dataModelService);
|
||||
}
|
||||
public Func<object, bool> CompiledListPredicate { get; private set; }
|
||||
|
||||
public void UpdateList(DataModel dataModel, string path)
|
||||
{
|
||||
@ -111,35 +52,322 @@ namespace Artemis.Core.Models.Profile.Conditions
|
||||
|
||||
if (dataModel != null)
|
||||
{
|
||||
if (!dataModel.ContainsPath(path))
|
||||
throw new ArtemisCoreException($"Data model of type {dataModel.GetType().Name} does not contain a property at path '{path}'");
|
||||
if (dataModel.GetListTypeAtPath(path) == null)
|
||||
throw new ArtemisCoreException($"The path '{path}' does not contain a list");
|
||||
var listType = dataModel.GetListTypeAtPath(path);
|
||||
if (listType == null)
|
||||
throw new ArtemisCoreException($"Data model of type {dataModel.GetType().Name} does not contain a list at path '{path}'");
|
||||
|
||||
ListType = listType;
|
||||
}
|
||||
else
|
||||
{
|
||||
ListType = null;
|
||||
}
|
||||
|
||||
ListDataModel = dataModel;
|
||||
ListPropertyPath = path;
|
||||
|
||||
if (dataModel != null)
|
||||
{
|
||||
var parameter = Expression.Parameter(typeof(object), "listDataModel");
|
||||
var accessor = path.Split('.').Aggregate<string, Expression>(
|
||||
Expression.Convert(parameter, dataModel.GetType()),
|
||||
(expression, s) => Expression.Convert(Expression.Property(expression, s), typeof(IList)));
|
||||
if (!ListContainsInnerPath(LeftPropertyPath))
|
||||
LeftPropertyPath = null;
|
||||
if (!ListContainsInnerPath(RightPropertyPath))
|
||||
RightPropertyPath = null;
|
||||
|
||||
var lambda = Expression.Lambda<Func<object, IList>>(accessor, parameter);
|
||||
CompiledListAccessor = lambda.Compile();
|
||||
CreateExpression();
|
||||
}
|
||||
|
||||
public void UpdateLeftSide(string path)
|
||||
{
|
||||
if (!ListContainsInnerPath(path))
|
||||
throw new ArtemisCoreException($"List type {ListType.Name} does not contain path {path}");
|
||||
|
||||
LeftPropertyPath = path;
|
||||
|
||||
ValidateOperator();
|
||||
ValidateRightSide();
|
||||
CreateExpression();
|
||||
}
|
||||
|
||||
public void UpdateRightSideDynamic(string path)
|
||||
{
|
||||
if (!ListContainsInnerPath(path))
|
||||
throw new ArtemisCoreException($"List type {ListType.Name} does not contain path {path}");
|
||||
|
||||
PredicateType = PredicateType.Dynamic;
|
||||
RightPropertyPath = path;
|
||||
|
||||
CreateExpression();
|
||||
}
|
||||
|
||||
public void UpdateRightSideStatic(object staticValue)
|
||||
{
|
||||
PredicateType = PredicateType.Static;
|
||||
RightPropertyPath = null;
|
||||
|
||||
SetStaticValue(staticValue);
|
||||
CreateExpression();
|
||||
}
|
||||
|
||||
public void UpdateOperator(DisplayConditionOperator displayConditionOperator)
|
||||
{
|
||||
if (displayConditionOperator == null)
|
||||
{
|
||||
Operator = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (LeftPropertyPath == null)
|
||||
{
|
||||
Operator = displayConditionOperator;
|
||||
return;
|
||||
}
|
||||
|
||||
var leftType = GetTypeAtInnerPath(LeftPropertyPath);
|
||||
if (displayConditionOperator.SupportsType(leftType))
|
||||
Operator = displayConditionOperator;
|
||||
|
||||
CreateExpression();
|
||||
}
|
||||
|
||||
private void CreateExpression()
|
||||
{
|
||||
CompiledListPredicate = null;
|
||||
|
||||
if (Operator == null)
|
||||
return;
|
||||
|
||||
// If the operator does not support a right side, create a static expression because the right side will simply be null
|
||||
if (PredicateType == PredicateType.Dynamic && Operator.SupportsRightSide)
|
||||
CreateDynamicExpression();
|
||||
|
||||
CreateStaticExpression();
|
||||
}
|
||||
|
||||
internal override void ApplyToEntity()
|
||||
{
|
||||
Entity.PredicateType = (int) PredicateType;
|
||||
Entity.ListDataModelGuid = ListDataModel?.PluginInfo?.Guid;
|
||||
Entity.ListPropertyPath = ListPropertyPath;
|
||||
|
||||
Entity.LeftPropertyPath = LeftPropertyPath;
|
||||
Entity.RightPropertyPath = RightPropertyPath;
|
||||
Entity.RightStaticValue = JsonConvert.SerializeObject(RightStaticValue);
|
||||
|
||||
Entity.OperatorPluginGuid = Operator?.PluginInfo?.Guid;
|
||||
Entity.OperatorType = Operator?.GetType().Name;
|
||||
}
|
||||
|
||||
public override bool Evaluate()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool EvaluateObject(object target)
|
||||
{
|
||||
return CompiledListPredicate != null && CompiledListPredicate(target);
|
||||
}
|
||||
|
||||
internal override void Initialize(IDataModelService dataModelService)
|
||||
{
|
||||
// Left side
|
||||
if (Entity.LeftPropertyPath != null && ListContainsInnerPath(Entity.LeftPropertyPath))
|
||||
UpdateLeftSide(Entity.LeftPropertyPath);
|
||||
|
||||
// Operator
|
||||
if (Entity.OperatorPluginGuid != null)
|
||||
{
|
||||
var conditionOperator = dataModelService.GetConditionOperator(Entity.OperatorPluginGuid.Value, Entity.OperatorType);
|
||||
if (conditionOperator != null)
|
||||
UpdateOperator(conditionOperator);
|
||||
}
|
||||
|
||||
// Right side dynamic
|
||||
if (PredicateType == PredicateType.Dynamic && Entity.RightPropertyPath != null)
|
||||
{
|
||||
if (ListContainsInnerPath(Entity.RightPropertyPath))
|
||||
UpdateLeftSide(Entity.LeftPropertyPath);
|
||||
}
|
||||
// Right side static
|
||||
else if (PredicateType == PredicateType.Static && Entity.RightStaticValue != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (LeftPropertyPath != null)
|
||||
{
|
||||
// Use the left side type so JSON.NET has a better idea what to do
|
||||
var leftSideType = GetTypeAtInnerPath(LeftPropertyPath);
|
||||
object rightSideValue;
|
||||
|
||||
try
|
||||
{
|
||||
rightSideValue = JsonConvert.DeserializeObject(Entity.RightStaticValue, leftSideType);
|
||||
}
|
||||
// If deserialization fails, use the type's default
|
||||
catch (JsonSerializationException e)
|
||||
{
|
||||
dataModelService.LogListPredicateDeserializationFailure(this, e);
|
||||
rightSideValue = Activator.CreateInstance(leftSideType);
|
||||
}
|
||||
|
||||
UpdateRightSideStatic(rightSideValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Hope for the best... we must infer the type from JSON now
|
||||
UpdateRightSideStatic(JsonConvert.DeserializeObject(Entity.RightStaticValue));
|
||||
}
|
||||
}
|
||||
catch (JsonException e)
|
||||
{
|
||||
dataModelService.LogListPredicateDeserializationFailure(this, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Func<object, IList> CompiledListAccessor { get; set; }
|
||||
}
|
||||
internal override DisplayConditionPartEntity GetEntity()
|
||||
{
|
||||
return Entity;
|
||||
}
|
||||
|
||||
public enum ListOperator
|
||||
{
|
||||
Any,
|
||||
All,
|
||||
None,
|
||||
Count
|
||||
private void ValidateOperator()
|
||||
{
|
||||
if (LeftPropertyPath == null || Operator == null)
|
||||
return;
|
||||
|
||||
var leftSideType = GetTypeAtInnerPath(LeftPropertyPath);
|
||||
if (!Operator.SupportsType(leftSideType))
|
||||
Operator = null;
|
||||
}
|
||||
|
||||
private void ValidateRightSide()
|
||||
{
|
||||
var leftSideType = GetTypeAtInnerPath(LeftPropertyPath);
|
||||
if (PredicateType == PredicateType.Dynamic)
|
||||
{
|
||||
if (RightPropertyPath == null)
|
||||
return;
|
||||
|
||||
var rightSideType = GetTypeAtInnerPath(RightPropertyPath);
|
||||
if (!leftSideType.IsCastableFrom(rightSideType))
|
||||
UpdateRightSideDynamic(null);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (RightStaticValue != null && leftSideType.IsCastableFrom(RightStaticValue.GetType()))
|
||||
UpdateRightSideStatic(RightStaticValue);
|
||||
else
|
||||
UpdateRightSideStatic(null);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetStaticValue(object staticValue)
|
||||
{
|
||||
// If the left side is empty simply apply the value, any validation will wait
|
||||
if (LeftPropertyPath == null)
|
||||
{
|
||||
RightStaticValue = staticValue;
|
||||
return;
|
||||
}
|
||||
|
||||
var leftSideType = GetTypeAtInnerPath(LeftPropertyPath);
|
||||
|
||||
// If not null ensure the types match and if not, convert it
|
||||
if (staticValue != null && staticValue.GetType() == leftSideType)
|
||||
RightStaticValue = staticValue;
|
||||
else if (staticValue != null)
|
||||
RightStaticValue = Convert.ChangeType(staticValue, leftSideType);
|
||||
// If null create a default instance for value types or simply make it null for reference types
|
||||
else if (leftSideType.IsValueType)
|
||||
RightStaticValue = Activator.CreateInstance(leftSideType);
|
||||
else
|
||||
RightStaticValue = null;
|
||||
}
|
||||
|
||||
private void CreateDynamicExpression()
|
||||
{
|
||||
if (LeftPropertyPath == null || RightPropertyPath == null || Operator == null)
|
||||
return;
|
||||
|
||||
// List accessors share the same parameter because a list always contains one item per entry
|
||||
var leftSideParameter = Expression.Parameter(typeof(object), "listItem");
|
||||
var leftSideAccessor = CreateListAccessor(LeftPropertyPath, leftSideParameter);
|
||||
var rightSideAccessor = CreateListAccessor(RightPropertyPath, leftSideParameter);
|
||||
|
||||
// A conversion may be required if the types differ
|
||||
// This can cause issues if the DisplayConditionOperator wasn't accurate in it's supported types but that is not a concern here
|
||||
if (rightSideAccessor.Type != leftSideAccessor.Type)
|
||||
rightSideAccessor = Expression.Convert(rightSideAccessor, leftSideAccessor.Type);
|
||||
|
||||
var conditionExpression = Operator.CreateExpression(leftSideAccessor, rightSideAccessor);
|
||||
var lambda = Expression.Lambda<Func<object, bool>>(conditionExpression, leftSideParameter);
|
||||
CompiledListPredicate = lambda.Compile();
|
||||
}
|
||||
|
||||
private void CreateStaticExpression()
|
||||
{
|
||||
if (LeftPropertyPath == null || Operator == null)
|
||||
return;
|
||||
|
||||
// List accessors share the same parameter because a list always contains one item per entry
|
||||
var leftSideParameter = Expression.Parameter(typeof(object), "listItem");
|
||||
var leftSideAccessor = CreateListAccessor(LeftPropertyPath, leftSideParameter);
|
||||
|
||||
// If the left side is a value type but the input is empty, this isn't a valid expression
|
||||
if (leftSideAccessor.Type.IsValueType && RightStaticValue == null)
|
||||
return;
|
||||
|
||||
// If the right side value is null, the constant type cannot be inferred and must be provided manually
|
||||
var rightSideConstant = RightStaticValue != null
|
||||
? Expression.Constant(RightStaticValue)
|
||||
: Expression.Constant(null, leftSideAccessor.Type);
|
||||
|
||||
var conditionExpression = Operator.CreateExpression(leftSideAccessor, rightSideConstant);
|
||||
var lambda = Expression.Lambda<Func<object, bool>>(conditionExpression, leftSideParameter);
|
||||
CompiledListPredicate = lambda.Compile();
|
||||
}
|
||||
|
||||
private Expression CreateListAccessor(string path, ParameterExpression listParameter)
|
||||
{
|
||||
return path.Split('.').Aggregate<string, Expression>(
|
||||
Expression.Convert(listParameter, ListType), // Cast to the appropriate type
|
||||
Expression.Property
|
||||
);
|
||||
}
|
||||
|
||||
public bool ListContainsInnerPath(string path)
|
||||
{
|
||||
if (ListType == null)
|
||||
return false;
|
||||
|
||||
var parts = path.Split('.');
|
||||
var current = ListType;
|
||||
foreach (var part in parts)
|
||||
{
|
||||
var property = current.GetProperty(part);
|
||||
current = property?.PropertyType;
|
||||
|
||||
if (property == null)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public Type GetTypeAtInnerPath(string path)
|
||||
{
|
||||
if (!ListContainsInnerPath(path))
|
||||
return null;
|
||||
|
||||
var parts = path.Split('.');
|
||||
var current = ListType;
|
||||
|
||||
Type result = null;
|
||||
foreach (var part in parts)
|
||||
{
|
||||
var property = current.GetProperty(part);
|
||||
current = property.PropertyType;
|
||||
result = property.PropertyType;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -18,17 +18,17 @@ namespace Artemis.Core.Models.Profile.Conditions
|
||||
{
|
||||
Parent = parent;
|
||||
PredicateType = predicateType;
|
||||
DisplayConditionPredicateEntity = new DisplayConditionPredicateEntity();
|
||||
Entity = new DisplayConditionPredicateEntity();
|
||||
}
|
||||
|
||||
public DisplayConditionPredicate(DisplayConditionPart parent, DisplayConditionPredicateEntity entity)
|
||||
{
|
||||
Parent = parent;
|
||||
DisplayConditionPredicateEntity = entity;
|
||||
Entity = entity;
|
||||
PredicateType = (PredicateType) entity.PredicateType;
|
||||
}
|
||||
|
||||
public DisplayConditionPredicateEntity DisplayConditionPredicateEntity { get; set; }
|
||||
public DisplayConditionPredicateEntity Entity { get; set; }
|
||||
|
||||
public PredicateType PredicateType { get; set; }
|
||||
public DisplayConditionOperator Operator { get; private set; }
|
||||
@ -38,6 +38,8 @@ namespace Artemis.Core.Models.Profile.Conditions
|
||||
public DataModel RightDataModel { get; private set; }
|
||||
public string RightPropertyPath { get; private set; }
|
||||
public object RightStaticValue { get; private set; }
|
||||
public DataModel ListDataModel { get; private set; }
|
||||
public string ListPropertyPath { get; private set; }
|
||||
|
||||
public Func<DataModel, DataModel, bool> CompiledDynamicPredicate { get; private set; }
|
||||
public Func<DataModel, bool> CompiledStaticPredicate { get; private set; }
|
||||
@ -135,16 +137,16 @@ namespace Artemis.Core.Models.Profile.Conditions
|
||||
|
||||
internal override void ApplyToEntity()
|
||||
{
|
||||
DisplayConditionPredicateEntity.PredicateType = (int) PredicateType;
|
||||
DisplayConditionPredicateEntity.LeftDataModelGuid = LeftDataModel?.PluginInfo?.Guid;
|
||||
DisplayConditionPredicateEntity.LeftPropertyPath = LeftPropertyPath;
|
||||
Entity.PredicateType = (int) PredicateType;
|
||||
Entity.LeftDataModelGuid = LeftDataModel?.PluginInfo?.Guid;
|
||||
Entity.LeftPropertyPath = LeftPropertyPath;
|
||||
|
||||
DisplayConditionPredicateEntity.RightDataModelGuid = RightDataModel?.PluginInfo?.Guid;
|
||||
DisplayConditionPredicateEntity.RightPropertyPath = RightPropertyPath;
|
||||
DisplayConditionPredicateEntity.RightStaticValue = JsonConvert.SerializeObject(RightStaticValue);
|
||||
Entity.RightDataModelGuid = RightDataModel?.PluginInfo?.Guid;
|
||||
Entity.RightPropertyPath = RightPropertyPath;
|
||||
Entity.RightStaticValue = JsonConvert.SerializeObject(RightStaticValue);
|
||||
|
||||
DisplayConditionPredicateEntity.OperatorPluginGuid = Operator?.PluginInfo?.Guid;
|
||||
DisplayConditionPredicateEntity.OperatorType = Operator?.GetType().Name;
|
||||
Entity.OperatorPluginGuid = Operator?.PluginInfo?.Guid;
|
||||
Entity.OperatorType = Operator?.GetType().Name;
|
||||
}
|
||||
|
||||
public override bool Evaluate()
|
||||
@ -168,30 +170,30 @@ namespace Artemis.Core.Models.Profile.Conditions
|
||||
internal override void Initialize(IDataModelService dataModelService)
|
||||
{
|
||||
// Left side
|
||||
if (DisplayConditionPredicateEntity.LeftDataModelGuid != null)
|
||||
if (Entity.LeftDataModelGuid != null)
|
||||
{
|
||||
var dataModel = dataModelService.GetPluginDataModelByGuid(DisplayConditionPredicateEntity.LeftDataModelGuid.Value);
|
||||
if (dataModel != null && dataModel.ContainsPath(DisplayConditionPredicateEntity.LeftPropertyPath))
|
||||
UpdateLeftSide(dataModel, DisplayConditionPredicateEntity.LeftPropertyPath);
|
||||
var dataModel = dataModelService.GetPluginDataModelByGuid(Entity.LeftDataModelGuid.Value);
|
||||
if (dataModel != null && dataModel.ContainsPath(Entity.LeftPropertyPath))
|
||||
UpdateLeftSide(dataModel, Entity.LeftPropertyPath);
|
||||
}
|
||||
|
||||
// Operator
|
||||
if (DisplayConditionPredicateEntity.OperatorPluginGuid != null)
|
||||
if (Entity.OperatorPluginGuid != null)
|
||||
{
|
||||
var conditionOperator = dataModelService.GetConditionOperator(DisplayConditionPredicateEntity.OperatorPluginGuid.Value, DisplayConditionPredicateEntity.OperatorType);
|
||||
var conditionOperator = dataModelService.GetConditionOperator(Entity.OperatorPluginGuid.Value, Entity.OperatorType);
|
||||
if (conditionOperator != null)
|
||||
UpdateOperator(conditionOperator);
|
||||
}
|
||||
|
||||
// Right side dynamic
|
||||
if (PredicateType == PredicateType.Dynamic && DisplayConditionPredicateEntity.RightDataModelGuid != null)
|
||||
if (PredicateType == PredicateType.Dynamic && Entity.RightDataModelGuid != null)
|
||||
{
|
||||
var dataModel = dataModelService.GetPluginDataModelByGuid(DisplayConditionPredicateEntity.RightDataModelGuid.Value);
|
||||
if (dataModel != null && dataModel.ContainsPath(DisplayConditionPredicateEntity.RightPropertyPath))
|
||||
UpdateRightSide(dataModel, DisplayConditionPredicateEntity.RightPropertyPath);
|
||||
var dataModel = dataModelService.GetPluginDataModelByGuid(Entity.RightDataModelGuid.Value);
|
||||
if (dataModel != null && dataModel.ContainsPath(Entity.RightPropertyPath))
|
||||
UpdateRightSide(dataModel, Entity.RightPropertyPath);
|
||||
}
|
||||
// Right side static
|
||||
else if (PredicateType == PredicateType.Static && DisplayConditionPredicateEntity.RightStaticValue != null)
|
||||
else if (PredicateType == PredicateType.Static && Entity.RightStaticValue != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -203,12 +205,12 @@ namespace Artemis.Core.Models.Profile.Conditions
|
||||
|
||||
try
|
||||
{
|
||||
rightSideValue = JsonConvert.DeserializeObject(DisplayConditionPredicateEntity.RightStaticValue, leftSideType);
|
||||
rightSideValue = JsonConvert.DeserializeObject(Entity.RightStaticValue, leftSideType);
|
||||
}
|
||||
// If deserialization fails, use the type's default
|
||||
catch (JsonSerializationException e)
|
||||
{
|
||||
dataModelService.LogDeserializationFailure(this, e);
|
||||
dataModelService.LogPredicateDeserializationFailure(this, e);
|
||||
rightSideValue = Activator.CreateInstance(leftSideType);
|
||||
}
|
||||
|
||||
@ -217,7 +219,7 @@ namespace Artemis.Core.Models.Profile.Conditions
|
||||
else
|
||||
{
|
||||
// Hope for the best...
|
||||
UpdateRightSide(JsonConvert.DeserializeObject(DisplayConditionPredicateEntity.RightStaticValue));
|
||||
UpdateRightSide(JsonConvert.DeserializeObject(Entity.RightStaticValue));
|
||||
}
|
||||
}
|
||||
catch (JsonReaderException)
|
||||
@ -230,7 +232,7 @@ namespace Artemis.Core.Models.Profile.Conditions
|
||||
|
||||
internal override DisplayConditionPartEntity GetEntity()
|
||||
{
|
||||
return DisplayConditionPredicateEntity;
|
||||
return Entity;
|
||||
}
|
||||
|
||||
private void ValidateOperator()
|
||||
@ -292,7 +294,7 @@ namespace Artemis.Core.Models.Profile.Conditions
|
||||
if (LeftDataModel == null || RightDataModel == null || Operator == null)
|
||||
return;
|
||||
|
||||
var isListExpression = LeftDataModel.GetListTypeAtPath(LeftPropertyPath) != null;
|
||||
var isListExpression = LeftDataModel.GetListTypeInPath(LeftPropertyPath) != null;
|
||||
|
||||
Expression leftSideAccessor;
|
||||
Expression rightSideAccessor;
|
||||
@ -335,7 +337,7 @@ namespace Artemis.Core.Models.Profile.Conditions
|
||||
if (LeftDataModel == null || Operator == null)
|
||||
return;
|
||||
|
||||
var isListExpression = LeftDataModel.GetListTypeAtPath(LeftPropertyPath) != null;
|
||||
var isListExpression = LeftDataModel.GetListTypeInPath(LeftPropertyPath) != null;
|
||||
|
||||
Expression leftSideAccessor;
|
||||
ParameterExpression leftSideParameter;
|
||||
@ -373,7 +375,7 @@ namespace Artemis.Core.Models.Profile.Conditions
|
||||
|
||||
private Expression CreateAccessor(DataModel dataModel, string path, string parameterName, out ParameterExpression parameter)
|
||||
{
|
||||
var listType = dataModel.GetListTypeAtPath(path);
|
||||
var listType = dataModel.GetListTypeInPath(path);
|
||||
if (listType != null)
|
||||
throw new ArtemisCoreException($"Cannot create a regular accessor at path {path} because the path contains a list");
|
||||
|
||||
@ -386,7 +388,7 @@ namespace Artemis.Core.Models.Profile.Conditions
|
||||
|
||||
private Expression CreateListAccessor(DataModel dataModel, string path, ParameterExpression listParameter)
|
||||
{
|
||||
var listType = dataModel.GetListTypeAtPath(path);
|
||||
var listType = dataModel.GetListTypeInPath(path);
|
||||
if (listType == null)
|
||||
throw new ArtemisCoreException($"Cannot create a list accessor at path {path} because the path does not contain a list");
|
||||
|
||||
|
||||
@ -299,7 +299,7 @@ namespace Artemis.Core.Models.Profile
|
||||
ApplyRenderElementToEntity();
|
||||
|
||||
// Conditions
|
||||
RenderElementEntity.RootDisplayCondition = DisplayConditionGroup?.DisplayConditionGroupEntity;
|
||||
RenderElementEntity.RootDisplayCondition = DisplayConditionGroup?.Entity;
|
||||
DisplayConditionGroup?.ApplyToEntity();
|
||||
}
|
||||
|
||||
|
||||
@ -206,7 +206,7 @@ namespace Artemis.Core.Models.Profile
|
||||
}
|
||||
|
||||
// Conditions
|
||||
RenderElementEntity.RootDisplayCondition = DisplayConditionGroup?.DisplayConditionGroupEntity;
|
||||
RenderElementEntity.RootDisplayCondition = DisplayConditionGroup?.Entity;
|
||||
DisplayConditionGroup?.ApplyToEntity();
|
||||
}
|
||||
|
||||
|
||||
@ -31,14 +31,8 @@ namespace Artemis.Core.Plugins.Abstract.DataModels
|
||||
var current = GetType();
|
||||
foreach (var part in parts)
|
||||
{
|
||||
var property = current.GetProperty(part);
|
||||
|
||||
// For lists, look into the list type instead of the list itself
|
||||
if (property != null && typeof(IList).IsAssignableFrom(property.PropertyType))
|
||||
current = property.PropertyType.GetGenericArguments()[0];
|
||||
else
|
||||
current = property?.PropertyType;
|
||||
|
||||
var property = current?.GetProperty(part);
|
||||
current = property?.PropertyType;
|
||||
if (property == null)
|
||||
return false;
|
||||
}
|
||||
@ -58,20 +52,14 @@ namespace Artemis.Core.Plugins.Abstract.DataModels
|
||||
foreach (var part in parts)
|
||||
{
|
||||
var property = current.GetProperty(part);
|
||||
|
||||
// For lists, look into the list type instead of the list itself
|
||||
if (typeof(IList).IsAssignableFrom(property.PropertyType))
|
||||
current = property.PropertyType.GetGenericArguments()[0];
|
||||
else
|
||||
current = property.PropertyType;
|
||||
|
||||
current = property.PropertyType;
|
||||
result = property.PropertyType;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public Type GetListTypeAtPath(string path)
|
||||
public Type GetListTypeInPath(string path)
|
||||
{
|
||||
if (!ContainsPath(path))
|
||||
return null;
|
||||
@ -79,8 +67,13 @@ namespace Artemis.Core.Plugins.Abstract.DataModels
|
||||
var parts = path.Split('.');
|
||||
var current = GetType();
|
||||
|
||||
var index = 0;
|
||||
foreach (var part in parts)
|
||||
{
|
||||
// Only return a type if the path CONTAINS a list, not if it points TO a list
|
||||
if (index == parts.Length - 1)
|
||||
return null;
|
||||
|
||||
var property = current.GetProperty(part);
|
||||
|
||||
// For lists, look into the list type instead of the list itself
|
||||
@ -88,14 +81,24 @@ namespace Artemis.Core.Plugins.Abstract.DataModels
|
||||
return property.PropertyType.GetGenericArguments()[0];
|
||||
|
||||
current = property.PropertyType;
|
||||
index++;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Type GetListTypeAtPath(string path)
|
||||
{
|
||||
if (!ContainsPath(path))
|
||||
return null;
|
||||
|
||||
var child = GetTypeAtPath(path);
|
||||
return child.GenericTypeArguments.Length > 0 ? child.GenericTypeArguments[0] : null;
|
||||
}
|
||||
|
||||
public string GetListInnerPath(string path)
|
||||
{
|
||||
if (GetListTypeAtPath(path) == null)
|
||||
if (GetListTypeInPath(path) == null)
|
||||
throw new ArtemisCoreException($"Cannot determine inner list path at {path} because it does not contain a list");
|
||||
|
||||
var parts = path.Split('.');
|
||||
|
||||
@ -178,9 +178,27 @@ namespace Artemis.Core.Services
|
||||
return RegisteredConditionOperators.FirstOrDefault(o => o.PluginInfo.Guid == operatorPluginGuid && o.GetType().Name == operatorType);
|
||||
}
|
||||
|
||||
public void LogDeserializationFailure(DisplayConditionPredicate displayConditionPredicate, JsonSerializationException exception)
|
||||
public void LogPredicateDeserializationFailure(DisplayConditionPredicate displayConditionPredicate, JsonException exception)
|
||||
{
|
||||
_logger.Warning(exception, "Failed to deserialize display condition predicate {predicate}", displayConditionPredicate);
|
||||
_logger.Warning(
|
||||
exception,
|
||||
"Failed to deserialize display condition predicate {left} {operator} {right}",
|
||||
displayConditionPredicate.Entity.LeftPropertyPath,
|
||||
displayConditionPredicate.Entity.OperatorType,
|
||||
displayConditionPredicate.Entity.RightPropertyPath
|
||||
);
|
||||
}
|
||||
|
||||
public void LogListPredicateDeserializationFailure(DisplayConditionListPredicate displayConditionPredicate, JsonException exception)
|
||||
{
|
||||
_logger.Warning(
|
||||
exception,
|
||||
"Failed to deserialize display condition list predicate {list} => {left} {operator} {right}",
|
||||
displayConditionPredicate.Entity.ListPropertyPath,
|
||||
displayConditionPredicate.Entity.LeftPropertyPath,
|
||||
displayConditionPredicate.Entity.OperatorType,
|
||||
displayConditionPredicate.Entity.RightPropertyPath
|
||||
);
|
||||
}
|
||||
|
||||
private void RegisterBuiltInConditionOperators()
|
||||
|
||||
@ -60,6 +60,7 @@ namespace Artemis.Core.Services.Interfaces
|
||||
|
||||
List<DisplayConditionOperator> GetCompatibleConditionOperators(Type type);
|
||||
DisplayConditionOperator GetConditionOperator(Guid operatorPluginGuid, string operatorType);
|
||||
void LogDeserializationFailure(DisplayConditionPredicate displayConditionPredicate, JsonSerializationException exception);
|
||||
void LogPredicateDeserializationFailure(DisplayConditionPredicate displayConditionPredicate, JsonException exception);
|
||||
void LogListPredicateDeserializationFailure(DisplayConditionListPredicate displayConditionListPredicate, JsonException exception);
|
||||
}
|
||||
}
|
||||
@ -154,8 +154,15 @@ namespace Artemis.Core.Services
|
||||
? new DisplayConditionGroup(null, renderElement.RenderElementEntity.RootDisplayCondition)
|
||||
: new DisplayConditionGroup(null);
|
||||
|
||||
displayCondition.Initialize(_dataModelService);
|
||||
renderElement.DisplayConditionGroup = displayCondition;
|
||||
try
|
||||
{
|
||||
displayCondition.Initialize(_dataModelService);
|
||||
renderElement.DisplayConditionGroup = displayCondition;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Warning(e, $"Failed to init display conditions for {renderElement}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Artemis.Storage.Entities.Profile.Abstract;
|
||||
|
||||
namespace Artemis.Storage.Entities.Profile
|
||||
{
|
||||
public class DisplayConditionListEntity : DisplayConditionPartEntity
|
||||
{
|
||||
public DisplayConditionListEntity()
|
||||
{
|
||||
Children = new List<DisplayConditionPartEntity>();
|
||||
}
|
||||
|
||||
public Guid? ListDataModelGuid { get; set; }
|
||||
public string ListPropertyPath { get; set; }
|
||||
|
||||
public int ListOperator { get; set; }
|
||||
}
|
||||
}
|
||||
@ -1,19 +1,21 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Artemis.Storage.Entities.Profile.Abstract;
|
||||
|
||||
namespace Artemis.Storage.Entities.Profile
|
||||
{
|
||||
public class DisplayConditionListPredicateEntity : DisplayConditionPartEntity
|
||||
{
|
||||
public DisplayConditionListPredicateEntity()
|
||||
{
|
||||
Children = new List<DisplayConditionPartEntity>();
|
||||
}
|
||||
public int PredicateType { get; set; }
|
||||
|
||||
public Guid? ListDataModelGuid { get; set; }
|
||||
public string ListPropertyPath { get; set; }
|
||||
|
||||
public int ListOperator { get; set; }
|
||||
public string LeftPropertyPath { get; set; }
|
||||
public string RightPropertyPath { get; set; }
|
||||
// Stored as a string to be able to control serialization and deserialization ourselves
|
||||
public string RightStaticValue { get; set; }
|
||||
|
||||
public string OperatorType { get; set; }
|
||||
public Guid? OperatorPluginGuid { get; set; }
|
||||
}
|
||||
}
|
||||
@ -208,16 +208,15 @@ namespace Artemis.UI.Shared.DataModelVisualization.Shared
|
||||
|
||||
var path = propertyPath.Split(".");
|
||||
var currentPart = path.First();
|
||||
|
||||
if (IsRootViewModel)
|
||||
{
|
||||
var child = Children.FirstOrDefault(c => c.DataModel != null &&
|
||||
var child = Children.FirstOrDefault(c => c.DataModel != null &&
|
||||
c.DataModel.PluginInfo.Guid == dataModelGuid);
|
||||
return child?.GetChildByPath(dataModelGuid, propertyPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
var child = Children.FirstOrDefault(c => c.DataModel != null &&
|
||||
var child = Children.FirstOrDefault(c => c.DataModel != null &&
|
||||
c.DataModel.PluginInfo.Guid == dataModelGuid && c.PropertyInfo?.Name == currentPart);
|
||||
if (child == null)
|
||||
return null;
|
||||
|
||||
@ -0,0 +1,53 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Markup;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace Artemis.UI.DataTemplateSelectors
|
||||
{
|
||||
// Source: https://stackoverflow.com/a/33421573/5015269
|
||||
public class ComboBoxTemplateSelector : DataTemplateSelector
|
||||
{
|
||||
public DataTemplate SelectedItemTemplate { get; set; }
|
||||
public DataTemplateSelector SelectedItemTemplateSelector { get; set; }
|
||||
public DataTemplate DropdownItemsTemplate { get; set; }
|
||||
public DataTemplateSelector DropdownItemsTemplateSelector { get; set; }
|
||||
|
||||
public override DataTemplate SelectTemplate(object item, DependencyObject container)
|
||||
{
|
||||
var itemToCheck = container;
|
||||
|
||||
// Search up the visual tree, stopping at either a ComboBox or
|
||||
// a ComboBoxItem (or null). This will determine which template to use
|
||||
while (itemToCheck != null && !(itemToCheck is ComboBoxItem) && !(itemToCheck is ComboBox))
|
||||
itemToCheck = VisualTreeHelper.GetParent(itemToCheck);
|
||||
|
||||
// If you stopped at a ComboBoxItem, you're in the dropdown
|
||||
var inDropDown = itemToCheck is ComboBoxItem;
|
||||
|
||||
return inDropDown
|
||||
? DropdownItemsTemplate ?? DropdownItemsTemplateSelector?.SelectTemplate(item, container)
|
||||
: SelectedItemTemplate ?? SelectedItemTemplateSelector?.SelectTemplate(item, container);
|
||||
}
|
||||
}
|
||||
|
||||
public class ComboBoxTemplateSelectorExtension : MarkupExtension
|
||||
{
|
||||
public DataTemplate SelectedItemTemplate { get; set; }
|
||||
public DataTemplateSelector SelectedItemTemplateSelector { get; set; }
|
||||
public DataTemplate DropdownItemsTemplate { get; set; }
|
||||
public DataTemplateSelector DropdownItemsTemplateSelector { get; set; }
|
||||
|
||||
public override object ProvideValue(IServiceProvider serviceProvider)
|
||||
{
|
||||
return new ComboBoxTemplateSelector()
|
||||
{
|
||||
SelectedItemTemplate = SelectedItemTemplate,
|
||||
SelectedItemTemplateSelector = SelectedItemTemplateSelector,
|
||||
DropdownItemsTemplate = DropdownItemsTemplate,
|
||||
DropdownItemsTemplateSelector = DropdownItemsTemplateSelector
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -78,7 +78,7 @@ namespace Artemis.UI.Ninject.Factories
|
||||
public interface IDisplayConditionsVmFactory : IVmFactory
|
||||
{
|
||||
DisplayConditionGroupViewModel DisplayConditionGroupViewModel(DisplayConditionGroup displayConditionGroup, DisplayConditionViewModel parent);
|
||||
DisplayConditionListPredicateViewModel DisplayConditionListPredicateViewModel(DisplayConditionListPredicate displayConditionListPredicate, DisplayConditionViewModel parent);
|
||||
DisplayConditionListViewModel DisplayConditionListViewModel(DisplayConditionList displayConditionList, DisplayConditionViewModel parent);
|
||||
DisplayConditionPredicateViewModel DisplayConditionPredicateViewModel(DisplayConditionPredicate displayConditionPredicate, DisplayConditionViewModel parent);
|
||||
}
|
||||
|
||||
|
||||
@ -6,17 +6,18 @@
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:propertyInput="clr-namespace:Artemis.UI.PropertyInput"
|
||||
xmlns:layerBrush="clr-namespace:Artemis.Core.Plugins.LayerBrush;assembly=Artemis.Core"
|
||||
xmlns:dataTemplateSelectors="clr-namespace:Artemis.UI.DataTemplateSelectors"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800"
|
||||
d:DataContext="{d:DesignInstance {x:Type propertyInput:BrushPropertyInputViewModel}}">
|
||||
<UserControl.Resources>
|
||||
<ControlTemplate x:Key="SimpleTemplate">
|
||||
<DataTemplate x:Key="SimpleTemplate">
|
||||
<StackPanel d:DataContext="{d:DesignInstance {x:Type layerBrush:LayerBrushDescriptor}}" Orientation="Horizontal">
|
||||
<materialDesign:PackIcon Kind="{Binding Icon}" Height="13" Width="13" Margin="0 1 3 0" />
|
||||
<TextBlock Text="{Binding DisplayName}" />
|
||||
</StackPanel>
|
||||
</ControlTemplate>
|
||||
<ControlTemplate x:Key="ExtendedTemplate">
|
||||
</DataTemplate>
|
||||
<DataTemplate x:Key="ExtendedTemplate">
|
||||
<Grid d:DataContext="{d:DesignInstance {x:Type layerBrush:LayerBrushDescriptor}}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
@ -30,15 +31,8 @@
|
||||
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding DisplayName}" TextWrapping="Wrap" MaxWidth="350" />
|
||||
<TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding Description}" TextWrapping="Wrap" MaxWidth="350" Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}" />
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
<DataTemplate x:Key="DescriptorTemplate">
|
||||
<Control x:Name="TemplateControl" Focusable="False" Template="{StaticResource ExtendedTemplate}" />
|
||||
<DataTemplate.Triggers>
|
||||
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ComboBoxItem}}, Path=IsSelected}" Value="{x:Null}">
|
||||
<Setter TargetName="TemplateControl" Property="Template" Value="{StaticResource SimpleTemplate}" />
|
||||
</DataTrigger>
|
||||
</DataTemplate.Triggers>
|
||||
</DataTemplate>
|
||||
|
||||
</UserControl.Resources>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Width="10" Text="{Binding LayerProperty.PropertyDescription.InputPrefix}" />
|
||||
@ -51,7 +45,9 @@
|
||||
HorizontalAlignment="Left"
|
||||
ItemsSource="{Binding Path=Descriptors}"
|
||||
SelectedValue="{Binding Path=SelectedDescriptor}"
|
||||
ItemTemplate="{StaticResource DescriptorTemplate}" />
|
||||
ItemTemplateSelector="{dataTemplateSelectors:ComboBoxTemplateSelector
|
||||
SelectedItemTemplate={StaticResource SimpleTemplate},
|
||||
DropdownItemsTemplate={StaticResource ExtendedTemplate}}" />
|
||||
<TextBlock Width="10" Text="{Binding LayerProperty.PropertyDescription.InputAffix}" />
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
@ -71,7 +71,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions
|
||||
else if (type == "Dynamic")
|
||||
DisplayConditionGroup.AddChild(new DisplayConditionPredicate(DisplayConditionGroup, PredicateType.Dynamic));
|
||||
else if (type == "List")
|
||||
DisplayConditionGroup.AddChild(new DisplayConditionListPredicate(DisplayConditionGroup));
|
||||
DisplayConditionGroup.AddChild(new DisplayConditionList(DisplayConditionGroup));
|
||||
|
||||
Update();
|
||||
_profileEditorService.UpdateSelectedProfileElement();
|
||||
@ -108,8 +108,8 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions
|
||||
case DisplayConditionGroup displayConditionGroup:
|
||||
Children.Add(_displayConditionsVmFactory.DisplayConditionGroupViewModel(displayConditionGroup, this));
|
||||
break;
|
||||
case DisplayConditionListPredicate displayConditionListPredicate:
|
||||
Children.Add(_displayConditionsVmFactory.DisplayConditionListPredicateViewModel(displayConditionListPredicate, this));
|
||||
case DisplayConditionList displayConditionListPredicate:
|
||||
Children.Add(_displayConditionsVmFactory.DisplayConditionListViewModel(displayConditionListPredicate, this));
|
||||
break;
|
||||
case DisplayConditionPredicate displayConditionPredicate:
|
||||
Children.Add(_displayConditionsVmFactory.DisplayConditionPredicateViewModel(displayConditionPredicate, this));
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
<UserControl x:Class="Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions.DisplayConditionListPredicateView"
|
||||
<UserControl x:Class="Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions.DisplayConditionListView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
@ -10,7 +10,7 @@
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800"
|
||||
d:DataContext="{d:DesignInstance Type=local:DisplayConditionListPredicateViewModel, IsDesignTimeCreatable=False}">
|
||||
d:DataContext="{d:DesignInstance Type=local:DisplayConditionListViewModel, IsDesignTimeCreatable=False}">
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
@ -4,11 +4,11 @@ using System.Windows.Controls;
|
||||
namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for DisplayConditionListPredicateView.xaml
|
||||
/// Interaction logic for DisplayConditionListView.xaml
|
||||
/// </summary>
|
||||
public partial class DisplayConditionListPredicateView : UserControl
|
||||
public partial class DisplayConditionListView : UserControl
|
||||
{
|
||||
public DisplayConditionListPredicateView()
|
||||
public DisplayConditionListView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
@ -17,7 +17,7 @@ using Humanizer;
|
||||
|
||||
namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions
|
||||
{
|
||||
public class DisplayConditionListPredicateViewModel : DisplayConditionViewModel
|
||||
public class DisplayConditionListViewModel : DisplayConditionViewModel
|
||||
{
|
||||
private readonly IProfileEditorService _profileEditorService;
|
||||
private readonly IDataModelVisualizationService _dataModelVisualizationService;
|
||||
@ -27,13 +27,13 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions
|
||||
private DataModelPropertiesViewModel _targetDataModel;
|
||||
private readonly Timer _updateTimer;
|
||||
|
||||
public DisplayConditionListPredicateViewModel(
|
||||
DisplayConditionListPredicate displayConditionListPredicate,
|
||||
public DisplayConditionListViewModel(
|
||||
DisplayConditionList displayConditionList,
|
||||
DisplayConditionViewModel parent,
|
||||
IProfileEditorService profileEditorService,
|
||||
IDataModelVisualizationService dataModelVisualizationService,
|
||||
IDisplayConditionsVmFactory displayConditionsVmFactory,
|
||||
ISettingsService settingsService) : base(displayConditionListPredicate, parent)
|
||||
ISettingsService settingsService) : base(displayConditionList, parent)
|
||||
{
|
||||
_profileEditorService = profileEditorService;
|
||||
_dataModelVisualizationService = dataModelVisualizationService;
|
||||
@ -51,7 +51,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions
|
||||
public DelegateCommand SelectListPropertyCommand { get; }
|
||||
public PluginSetting<bool> ShowDataModelValues { get; }
|
||||
|
||||
public DisplayConditionListPredicate DisplayConditionListPredicate => (DisplayConditionListPredicate) Model;
|
||||
public DisplayConditionList DisplayConditionList => (DisplayConditionList) Model;
|
||||
|
||||
public bool IsInitialized
|
||||
{
|
||||
@ -73,21 +73,21 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions
|
||||
set => SetAndNotify(ref _selectedListProperty, value);
|
||||
}
|
||||
|
||||
public string SelectedListOperator => DisplayConditionListPredicate.ListOperator.Humanize();
|
||||
public string SelectedListOperator => DisplayConditionList.ListOperator.Humanize();
|
||||
|
||||
public void SelectListOperator(string type)
|
||||
{
|
||||
var enumValue = Enum.Parse<ListOperator>(type);
|
||||
DisplayConditionListPredicate.ListOperator = enumValue;
|
||||
DisplayConditionList.ListOperator = enumValue;
|
||||
NotifyOfPropertyChange(nameof(SelectedListOperator));
|
||||
}
|
||||
|
||||
public void AddCondition(string type)
|
||||
{
|
||||
if (type == "Static")
|
||||
DisplayConditionListPredicate.AddChild(new DisplayConditionPredicate(DisplayConditionListPredicate, PredicateType.Static));
|
||||
DisplayConditionList.AddChild(new DisplayConditionPredicate(DisplayConditionList, PredicateType.Static));
|
||||
else if (type == "Dynamic")
|
||||
DisplayConditionListPredicate.AddChild(new DisplayConditionPredicate(DisplayConditionListPredicate, PredicateType.Dynamic));
|
||||
DisplayConditionList.AddChild(new DisplayConditionPredicate(DisplayConditionList, PredicateType.Dynamic));
|
||||
|
||||
Update();
|
||||
_profileEditorService.UpdateSelectedProfileElement();
|
||||
@ -95,7 +95,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions
|
||||
|
||||
public void AddGroup()
|
||||
{
|
||||
DisplayConditionListPredicate.AddChild(new DisplayConditionGroup(DisplayConditionListPredicate));
|
||||
DisplayConditionList.AddChild(new DisplayConditionGroup(DisplayConditionList));
|
||||
|
||||
Update();
|
||||
_profileEditorService.UpdateSelectedProfileElement();
|
||||
@ -146,7 +146,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions
|
||||
|
||||
public void ApplyList()
|
||||
{
|
||||
DisplayConditionListPredicate.UpdateList(SelectedListProperty.DataModel, SelectedListProperty.PropertyPath);
|
||||
DisplayConditionList.UpdateList(SelectedListProperty.DataModel, SelectedListProperty.PropertyPath);
|
||||
_profileEditorService.UpdateSelectedProfileElement();
|
||||
|
||||
Update();
|
||||
@ -155,7 +155,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions
|
||||
public override DataModelPropertiesViewModel GetDataModelOverride()
|
||||
{
|
||||
if (SelectedListProperty != null)
|
||||
return (DataModelPropertiesViewModel) SelectedListProperty.GetListTypeViewModel(_dataModelVisualizationService);
|
||||
return SelectedListProperty.GetListTypeViewModel(_dataModelVisualizationService);
|
||||
|
||||
return base.GetDataModelOverride();
|
||||
}
|
||||
@ -168,9 +168,12 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions
|
||||
NotifyOfPropertyChange(nameof(SelectedListOperator));
|
||||
|
||||
// Update the selected list property
|
||||
if (DisplayConditionListPredicate.ListDataModel != null && DisplayConditionListPredicate.ListPropertyPath != null)
|
||||
if (DisplayConditionList.ListDataModel != null && DisplayConditionList.ListPropertyPath != null)
|
||||
{
|
||||
var child = TargetDataModel.GetChildByPath(DisplayConditionListPredicate.ListDataModel.PluginInfo.Guid, DisplayConditionListPredicate.ListPropertyPath);
|
||||
var child = TargetDataModel.GetChildByPath(
|
||||
DisplayConditionList.ListDataModel.PluginInfo.Guid,
|
||||
DisplayConditionList.ListPropertyPath
|
||||
);
|
||||
SelectedListProperty = child as DataModelListViewModel;
|
||||
}
|
||||
|
||||
@ -178,7 +181,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions
|
||||
TargetDataModel.ApplyTypeFilter(true, typeof(IList));
|
||||
|
||||
// Remove VMs of effects no longer applied on the layer
|
||||
var toRemove = Children.Where(c => !DisplayConditionListPredicate.Children.Contains(c.Model)).ToList();
|
||||
var toRemove = Children.Where(c => !DisplayConditionList.Children.Contains(c.Model)).ToList();
|
||||
// Using RemoveRange breaks our lovely animations
|
||||
foreach (var displayConditionViewModel in toRemove)
|
||||
{
|
||||
@ -8,12 +8,10 @@
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:converters="clr-namespace:Artemis.UI.Converters"
|
||||
xmlns:utilities="clr-namespace:Artemis.UI.Utilities"
|
||||
xmlns:dataModel="clr-namespace:Artemis.UI.Shared.DataModelVisualization.Shared;assembly=Artemis.UI.Shared"
|
||||
x:Class="Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions.DisplayConditionPredicateView"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800"
|
||||
d:DataContext="{d:DesignInstance IsDesignTimeCreatable=False, Type={x:Type local:DisplayConditionPredicateViewModel}}"
|
||||
x:Name="DisplayConditionPredicateRoot">
|
||||
d:DataContext="{d:DesignInstance IsDesignTimeCreatable=False, Type={x:Type local:DisplayConditionPredicateViewModel}}">
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
|
||||
@ -9,6 +9,7 @@
|
||||
xmlns:behaviors="clr-namespace:Artemis.UI.Behaviors"
|
||||
xmlns:profile="clr-namespace:Artemis.Core.Models.Profile;assembly=Artemis.Core"
|
||||
xmlns:layerBrush="clr-namespace:Artemis.Core.Plugins.LayerBrush;assembly=Artemis.Core"
|
||||
xmlns:dataTemplateSelectors="clr-namespace:Artemis.UI.DataTemplateSelectors"
|
||||
mc:Ignorable="d"
|
||||
behaviors:InputBindingBehavior.PropagateInputBindingsToWindow="True"
|
||||
d:DesignHeight="450" d:DesignWidth="800"
|
||||
@ -22,12 +23,12 @@
|
||||
Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.PopupBox.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
<ControlTemplate x:Key="SimpleTemplate">
|
||||
<DataTemplate x:Key="SimpleTemplate">
|
||||
<StackPanel d:DataContext="{d:DesignInstance {x:Type profile:ProfileDescriptor}}" Orientation="Horizontal">
|
||||
<TextBlock Text="{Binding Name}" />
|
||||
</StackPanel>
|
||||
</ControlTemplate>
|
||||
<ControlTemplate x:Key="ExtendedTemplate">
|
||||
</DataTemplate>
|
||||
<DataTemplate x:Key="ExtendedTemplate">
|
||||
<Grid d:DataContext="{d:DesignInstance {x:Type profile:ProfileDescriptor}}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
@ -44,14 +45,6 @@
|
||||
<materialDesign:PackIcon Kind="TrashCanOutline" Height="14" Width="14" HorizontalAlignment="Right" />
|
||||
</Button>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
<DataTemplate x:Key="ProfileDescriptorTemplate">
|
||||
<Control x:Name="TemplateControl" Focusable="False" Template="{StaticResource ExtendedTemplate}" />
|
||||
<DataTemplate.Triggers>
|
||||
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ComboBoxItem}}, Path=IsSelected}" Value="{x:Null}">
|
||||
<Setter TargetName="TemplateControl" Property="Template" Value="{StaticResource SimpleTemplate}" />
|
||||
</DataTrigger>
|
||||
</DataTemplate.Triggers>
|
||||
</DataTemplate>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
@ -138,7 +131,9 @@
|
||||
VerticalAlignment="Top"
|
||||
ItemsSource="{Binding Profiles}"
|
||||
SelectedItem="{Binding SelectedProfile}"
|
||||
ItemTemplate="{StaticResource ProfileDescriptorTemplate}">
|
||||
ItemTemplateSelector="{dataTemplateSelectors:ComboBoxTemplateSelector
|
||||
SelectedItemTemplate={StaticResource SimpleTemplate},
|
||||
DropdownItemsTemplate={StaticResource ExtendedTemplate}}" >
|
||||
<ComboBox.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<VirtualizingStackPanel />
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user