using FluentValidation; using FluentValidation.Results; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Dynamic; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Threading.Tasks; using zero.Core.Extensions; namespace zero.Core.Renderer { public class AbstractGenericRenderer : AbstractRenderer { public Type TargetType { get; set; } internal AbstractGenericRenderer(Type type, List properties, IValidator validator = null) { TargetType = type; Properties = properties; Validator = validator; } } public abstract class AbstractRenderer : IRenderer where T : new() { const string METHOD_FIELD = "field"; const string METHOD_TAB = "tab"; const string METHOD_BOX = "box"; internal List Properties = new List(); RenderProperty ParentProperty = null; int CurrentDepth = 0; protected string LabelTemplate = "{0}"; protected string DescriptionTemplate = "{0}"; protected IValidator Validator = null; public RendererConfig Build() { RendererConfig config = new RendererConfig(); config.Type = typeof(T); config.Validator = Validator; // compile fields static void compile(List properties) { foreach (RenderProperty property in properties) { property.Compile?.Invoke(property); if (property.Children.Count > 0) { compile(property.Children); } } } compile(Properties); // move top-level properties into tab in case other tabs exist int countTabs = Properties.Count(x => x.Method == METHOD_TAB); if (countTabs > 0 && countTabs < Properties.Count) { int index = Properties.FindIndex(x => x.Method != METHOD_TAB); List children = Properties.Where(x => x.Method != METHOD_TAB).ToList(); Properties.Insert(index, new RenderProperty() { Method = METHOD_TAB, Children = children, Params = new { Name = "@ui.tab_general" } }); Properties = Properties.Where(x => x.Method == METHOD_TAB).ToList(); } // map to result static RendererComponent Map(RenderProperty property) { RendererComponent component = new RendererComponent() { Method = property.Method, Params = property.Params }; if (property.Children.Count > 0) { component.Components = property.Children.Select(x => Map(x)).ToList(); } else if (property.NestedRenderer != null) { component.Components = property.NestedRenderer.Build().Components; } return component; } config.Components = Properties.Select(x => Map(x)).ToList(); return config; } public AbstractGenericRenderer ToGenericRenderer() { return new AbstractGenericRenderer(typeof(T), Properties, Validator); } protected virtual IRendererFieldBuilder Field(Expression> mapExpression, string label = null, string description = null, bool required = false) { RendererFieldBuilder builder = new RendererFieldBuilder(); Add(new RenderProperty() { Method = METHOD_FIELD, Compile = property => { MemberExpression memberExpression = null; if (mapExpression.Body is UnaryExpression) { UnaryExpression unaryExpression = (UnaryExpression)mapExpression.Body; memberExpression = (MemberExpression)unaryExpression.Operand; } else { memberExpression = (MemberExpression)mapExpression.Body; } string fieldName = memberExpression.Member.Name.ToCamelCase(); RendererFieldBuilder.Data fieldData = builder.Build(); property.NestedRenderer = fieldData.Renderer; property.Params = new { Field = fieldName, View = fieldData.View, ComponentPath = fieldData.ComponentPath, Options = fieldData.Options ?? new List() { }, Label = String.Format(LabelTemplate, label ?? fieldName), Description = String.Format(DescriptionTemplate, description ?? fieldName), Required = required }; } }); return builder; } protected virtual void Tab(string name, Action builder) { Add(new RenderProperty() { Method = METHOD_TAB, Builder = builder, Params = new { Name = name } }); } protected virtual void Box(string name, string description, Action builder) { Add(new RenderProperty() { Method = METHOD_BOX, Builder = builder, Params = new { Name = name, Description = description } }); } private void Add(RenderProperty property) { if (property.Method == METHOD_TAB && CurrentDepth > 0) { throw new Exception("Tabs have to be defined on the root level of the renderer"); } if (property.Method == METHOD_BOX && (CurrentDepth > 1 || (CurrentDepth == 1 && ParentProperty?.Method != METHOD_TAB))) { throw new Exception("Boxes have to be defined on the root level of the renderer or as direct descendents of a tab"); } if (CurrentDepth == 0) { Properties.Add(property); } else { ParentProperty.Children.Add(property); } if (property.Builder != null) { CurrentDepth += 1; RenderProperty previousParent = ParentProperty; ParentProperty = property; property.Builder(); CurrentDepth -= 1; ParentProperty = previousParent; } } } }