mirror of https://github.com/Squidex/squidex.git
committed by
GitHub
410 changed files with 9874 additions and 6531 deletions
@ -0,0 +1,77 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Diagnostics.Contracts; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Schemas |
||||
|
{ |
||||
|
public sealed class ArrayField : RootField<ArrayFieldProperties>, IArrayField |
||||
|
{ |
||||
|
private FieldCollection<NestedField> fields = FieldCollection<NestedField>.Empty; |
||||
|
|
||||
|
public IReadOnlyList<NestedField> Fields |
||||
|
{ |
||||
|
get { return fields.Ordered; } |
||||
|
} |
||||
|
|
||||
|
public IReadOnlyDictionary<long, NestedField> FieldsById |
||||
|
{ |
||||
|
get { return fields.ById; } |
||||
|
} |
||||
|
|
||||
|
public IReadOnlyDictionary<string, NestedField> FieldsByName |
||||
|
{ |
||||
|
get { return fields.ByName; } |
||||
|
} |
||||
|
|
||||
|
public ArrayField(long id, string name, Partitioning partitioning, ArrayFieldProperties properties) |
||||
|
: base(id, name, partitioning, properties) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
[Pure] |
||||
|
public ArrayField DeleteField(long fieldId) |
||||
|
{ |
||||
|
return Updatefields(f => f.Remove(fieldId)); |
||||
|
} |
||||
|
|
||||
|
[Pure] |
||||
|
public ArrayField ReorderFields(List<long> ids) |
||||
|
{ |
||||
|
return Updatefields(f => f.Reorder(ids)); |
||||
|
} |
||||
|
|
||||
|
[Pure] |
||||
|
public ArrayField AddField(NestedField field) |
||||
|
{ |
||||
|
return Updatefields(f => f.Add(field)); |
||||
|
} |
||||
|
|
||||
|
[Pure] |
||||
|
public ArrayField UpdateField(long fieldId, Func<NestedField, NestedField> updater) |
||||
|
{ |
||||
|
return Updatefields(f => f.Update(fieldId, updater)); |
||||
|
} |
||||
|
|
||||
|
private ArrayField Updatefields(Func<FieldCollection<NestedField>, FieldCollection<NestedField>> updater) |
||||
|
{ |
||||
|
var newFields = updater(fields); |
||||
|
|
||||
|
if (ReferenceEquals(newFields, fields)) |
||||
|
{ |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
return Clone<ArrayField>(clone => |
||||
|
{ |
||||
|
clone.fields = newFields; |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,40 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Schemas |
||||
|
{ |
||||
|
[TypeName("ArrayField")] |
||||
|
public sealed class ArrayFieldProperties : FieldProperties |
||||
|
{ |
||||
|
public int? MinItems { get; set; } |
||||
|
|
||||
|
public int? MaxItems { get; set; } |
||||
|
|
||||
|
public override T Accept<T>(IFieldPropertiesVisitor<T> visitor) |
||||
|
{ |
||||
|
return visitor.Visit(this); |
||||
|
} |
||||
|
|
||||
|
public override T Accept<T>(IFieldVisitor<T> visitor, IField field) |
||||
|
{ |
||||
|
return visitor.Visit((IArrayField)field); |
||||
|
} |
||||
|
|
||||
|
public override RootField CreateRootField(long id, string name, Partitioning partitioning) |
||||
|
{ |
||||
|
return Fields.Array(id, name, partitioning, this); |
||||
|
} |
||||
|
|
||||
|
public override NestedField CreateNestedField(long id, string name) |
||||
|
{ |
||||
|
throw new NotSupportedException(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,161 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Collections.Immutable; |
||||
|
using System.Diagnostics.Contracts; |
||||
|
using System.Linq; |
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Schemas |
||||
|
{ |
||||
|
public sealed class FieldCollection<T> : Cloneable<FieldCollection<T>> where T : IField |
||||
|
{ |
||||
|
public static readonly FieldCollection<T> Empty = new FieldCollection<T>(); |
||||
|
|
||||
|
private ImmutableArray<T> fieldsOrdered = ImmutableArray<T>.Empty; |
||||
|
private ImmutableDictionary<long, T> fieldsById; |
||||
|
private ImmutableDictionary<string, T> fieldsByName; |
||||
|
|
||||
|
public IReadOnlyList<T> Ordered |
||||
|
{ |
||||
|
get { return fieldsOrdered; } |
||||
|
} |
||||
|
|
||||
|
public IReadOnlyDictionary<long, T> ById |
||||
|
{ |
||||
|
get |
||||
|
{ |
||||
|
if (fieldsById == null) |
||||
|
{ |
||||
|
if (fieldsOrdered.Length == 0) |
||||
|
{ |
||||
|
fieldsById = ImmutableDictionary<long, T>.Empty; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
fieldsById = fieldsOrdered.ToImmutableDictionary(x => x.Id); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return fieldsById; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public IReadOnlyDictionary<string, T> ByName |
||||
|
{ |
||||
|
get |
||||
|
{ |
||||
|
if (fieldsByName == null) |
||||
|
{ |
||||
|
if (fieldsOrdered.Length == 0) |
||||
|
{ |
||||
|
fieldsByName = ImmutableDictionary<string, T>.Empty; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
fieldsByName = fieldsOrdered.ToImmutableDictionary(x => x.Name); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return fieldsByName; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private FieldCollection() |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public FieldCollection(T[] fields) |
||||
|
{ |
||||
|
Guard.NotNull(fields, nameof(fields)); |
||||
|
|
||||
|
fieldsOrdered = ImmutableArray.Create(fields); |
||||
|
} |
||||
|
|
||||
|
protected override void OnCloned() |
||||
|
{ |
||||
|
fieldsById = null; |
||||
|
fieldsByName = null; |
||||
|
} |
||||
|
|
||||
|
[Pure] |
||||
|
public FieldCollection<T> Remove(long fieldId) |
||||
|
{ |
||||
|
if (!ById.TryGetValue(fieldId, out var field)) |
||||
|
{ |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
return Clone(clone => |
||||
|
{ |
||||
|
clone.fieldsOrdered = fieldsOrdered.Remove(field); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
[Pure] |
||||
|
public FieldCollection<T> Reorder(List<long> ids) |
||||
|
{ |
||||
|
Guard.NotNull(ids, nameof(ids)); |
||||
|
|
||||
|
if (ids.Count != fieldsOrdered.Length || ids.Any(x => !ById.ContainsKey(x))) |
||||
|
{ |
||||
|
throw new ArgumentException("Ids must cover all fields.", nameof(ids)); |
||||
|
} |
||||
|
|
||||
|
return Clone(clone => |
||||
|
{ |
||||
|
clone.fieldsOrdered = fieldsOrdered.OrderBy(f => ids.IndexOf(f.Id)).ToImmutableArray(); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
[Pure] |
||||
|
public FieldCollection<T> Add(T field) |
||||
|
{ |
||||
|
Guard.NotNull(field, nameof(field)); |
||||
|
|
||||
|
if (ByName.ContainsKey(field.Name) || ById.ContainsKey(field.Id)) |
||||
|
{ |
||||
|
throw new ArgumentException($"A field with name '{field.Name}' and id {field.Id} already exists.", nameof(field)); |
||||
|
} |
||||
|
|
||||
|
return Clone(clone => |
||||
|
{ |
||||
|
clone.fieldsOrdered = clone.fieldsOrdered.Add(field); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
[Pure] |
||||
|
public FieldCollection<T> Update(long fieldId, Func<T, T> updater) |
||||
|
{ |
||||
|
Guard.NotNull(updater, nameof(updater)); |
||||
|
|
||||
|
if (!ById.TryGetValue(fieldId, out var field)) |
||||
|
{ |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
var newField = updater(field); |
||||
|
|
||||
|
if (ReferenceEquals(newField, field)) |
||||
|
{ |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
if (!(newField is T typedField)) |
||||
|
{ |
||||
|
throw new InvalidOperationException($"Field must be of type {typeof(T)}"); |
||||
|
} |
||||
|
|
||||
|
return Clone(clone => |
||||
|
{ |
||||
|
clone.fieldsOrdered = clone.fieldsOrdered.Replace(field, typedField); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,158 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Schemas |
||||
|
{ |
||||
|
public static class FieldExtensions |
||||
|
{ |
||||
|
public static Schema ReorderFields(this Schema schema, List<long> ids, long? parentId = null) |
||||
|
{ |
||||
|
if (parentId != null) |
||||
|
{ |
||||
|
return schema.UpdateField(parentId.Value, f => |
||||
|
{ |
||||
|
if (f is ArrayField arrayField) |
||||
|
{ |
||||
|
return arrayField.ReorderFields(ids); |
||||
|
} |
||||
|
|
||||
|
return f; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
return schema.ReorderFields(ids); |
||||
|
} |
||||
|
|
||||
|
public static Schema DeleteField(this Schema schema, long fieldId, long? parentId = null) |
||||
|
{ |
||||
|
if (parentId != null) |
||||
|
{ |
||||
|
return schema.UpdateField(parentId.Value, f => |
||||
|
{ |
||||
|
if (f is ArrayField arrayField) |
||||
|
{ |
||||
|
return arrayField.DeleteField(fieldId); |
||||
|
} |
||||
|
|
||||
|
return f; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
return schema.DeleteField(fieldId); |
||||
|
} |
||||
|
|
||||
|
public static Schema LockField(this Schema schema, long fieldId, long? parentId = null) |
||||
|
{ |
||||
|
if (parentId != null) |
||||
|
{ |
||||
|
return schema.UpdateField(parentId.Value, f => |
||||
|
{ |
||||
|
if (f is ArrayField arrayField) |
||||
|
{ |
||||
|
return arrayField.UpdateField(fieldId, n => n.Lock()); |
||||
|
} |
||||
|
|
||||
|
return f; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
return schema.UpdateField(fieldId, f => f.Lock()); |
||||
|
} |
||||
|
|
||||
|
public static Schema HideField(this Schema schema, long fieldId, long? parentId = null) |
||||
|
{ |
||||
|
if (parentId != null) |
||||
|
{ |
||||
|
return schema.UpdateField(parentId.Value, f => |
||||
|
{ |
||||
|
if (f is ArrayField arrayField) |
||||
|
{ |
||||
|
return arrayField.UpdateField(fieldId, n => n.Hide()); |
||||
|
} |
||||
|
|
||||
|
return f; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
return schema.UpdateField(fieldId, f => f.Hide()); |
||||
|
} |
||||
|
|
||||
|
public static Schema ShowField(this Schema schema, long fieldId, long? parentId = null) |
||||
|
{ |
||||
|
if (parentId != null) |
||||
|
{ |
||||
|
return schema.UpdateField(parentId.Value, f => |
||||
|
{ |
||||
|
if (f is ArrayField arrayField) |
||||
|
{ |
||||
|
return arrayField.UpdateField(fieldId, n => n.Show()); |
||||
|
} |
||||
|
|
||||
|
return f; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
return schema.UpdateField(fieldId, f => f.Show()); |
||||
|
} |
||||
|
|
||||
|
public static Schema EnableField(this Schema schema, long fieldId, long? parentId = null) |
||||
|
{ |
||||
|
if (parentId != null) |
||||
|
{ |
||||
|
return schema.UpdateField(parentId.Value, f => |
||||
|
{ |
||||
|
if (f is ArrayField arrayField) |
||||
|
{ |
||||
|
return arrayField.UpdateField(fieldId, n => n.Enable()); |
||||
|
} |
||||
|
|
||||
|
return f; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
return schema.UpdateField(fieldId, f => f.Enable()); |
||||
|
} |
||||
|
|
||||
|
public static Schema DisableField(this Schema schema, long fieldId, long? parentId = null) |
||||
|
{ |
||||
|
if (parentId != null) |
||||
|
{ |
||||
|
return schema.UpdateField(parentId.Value, f => |
||||
|
{ |
||||
|
if (f is ArrayField arrayField) |
||||
|
{ |
||||
|
return arrayField.UpdateField(fieldId, n => n.Disable()); |
||||
|
} |
||||
|
|
||||
|
return f; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
return schema.UpdateField(fieldId, f => f.Disable()); |
||||
|
} |
||||
|
|
||||
|
public static Schema UpdateField(this Schema schema, long fieldId, FieldProperties properties, long? parentId = null) |
||||
|
{ |
||||
|
if (parentId != null) |
||||
|
{ |
||||
|
return schema.UpdateField(parentId.Value, f => |
||||
|
{ |
||||
|
if (f is ArrayField arrayField) |
||||
|
{ |
||||
|
return arrayField.UpdateField(fieldId, n => n.Update(properties)); |
||||
|
} |
||||
|
|
||||
|
return f; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
return schema.UpdateField(fieldId, f => f.Update(properties)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,20 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Schemas |
||||
|
{ |
||||
|
public interface IArrayField : IField<ArrayFieldProperties> |
||||
|
{ |
||||
|
IReadOnlyList<NestedField> Fields { get; } |
||||
|
|
||||
|
IReadOnlyDictionary<long, NestedField> FieldsById { get; } |
||||
|
|
||||
|
IReadOnlyDictionary<string, NestedField> FieldsByName { get; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,13 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Schemas |
||||
|
{ |
||||
|
public interface INestedField : IField |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Schemas |
||||
|
{ |
||||
|
public interface IRootField : IField |
||||
|
{ |
||||
|
Partitioning Partitioning { get; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,29 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Newtonsoft.Json; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Schemas.Json |
||||
|
{ |
||||
|
public sealed class JsonNestedFieldModel |
||||
|
{ |
||||
|
[JsonProperty] |
||||
|
public long Id { get; set; } |
||||
|
|
||||
|
[JsonProperty] |
||||
|
public string Name { get; set; } |
||||
|
|
||||
|
[JsonProperty] |
||||
|
public bool IsHidden { get; set; } |
||||
|
|
||||
|
[JsonProperty] |
||||
|
public bool IsDisabled { get; set; } |
||||
|
|
||||
|
[JsonProperty] |
||||
|
public FieldProperties Properties { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,106 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Diagnostics.Contracts; |
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Schemas |
||||
|
{ |
||||
|
public abstract class NestedField : Cloneable<NestedField>, INestedField |
||||
|
{ |
||||
|
private readonly long fieldId; |
||||
|
private readonly string fieldName; |
||||
|
private bool isDisabled; |
||||
|
private bool isHidden; |
||||
|
private bool isLocked; |
||||
|
|
||||
|
public long Id |
||||
|
{ |
||||
|
get { return fieldId; } |
||||
|
} |
||||
|
|
||||
|
public string Name |
||||
|
{ |
||||
|
get { return fieldName; } |
||||
|
} |
||||
|
|
||||
|
public bool IsLocked |
||||
|
{ |
||||
|
get { return isLocked; } |
||||
|
} |
||||
|
|
||||
|
public bool IsHidden |
||||
|
{ |
||||
|
get { return isHidden; } |
||||
|
} |
||||
|
|
||||
|
public bool IsDisabled |
||||
|
{ |
||||
|
get { return isDisabled; } |
||||
|
} |
||||
|
|
||||
|
public abstract FieldProperties RawProperties { get; } |
||||
|
|
||||
|
protected NestedField(long id, string name) |
||||
|
{ |
||||
|
Guard.NotNullOrEmpty(name, nameof(name)); |
||||
|
Guard.GreaterThan(id, 0, nameof(id)); |
||||
|
|
||||
|
fieldId = id; |
||||
|
fieldName = name; |
||||
|
} |
||||
|
|
||||
|
[Pure] |
||||
|
public NestedField Lock() |
||||
|
{ |
||||
|
return Clone(clone => |
||||
|
{ |
||||
|
clone.isLocked = true; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
[Pure] |
||||
|
public NestedField Hide() |
||||
|
{ |
||||
|
return Clone(clone => |
||||
|
{ |
||||
|
clone.isHidden = true; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
[Pure] |
||||
|
public NestedField Show() |
||||
|
{ |
||||
|
return Clone(clone => |
||||
|
{ |
||||
|
clone.isHidden = false; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
[Pure] |
||||
|
public NestedField Disable() |
||||
|
{ |
||||
|
return Clone(clone => |
||||
|
{ |
||||
|
clone.isDisabled = true; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
[Pure] |
||||
|
public NestedField Enable() |
||||
|
{ |
||||
|
return Clone(clone => |
||||
|
{ |
||||
|
clone.isDisabled = false; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public abstract T Accept<T>(IFieldVisitor<T> visitor); |
||||
|
|
||||
|
public abstract NestedField Update(FieldProperties newProperties); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,70 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Diagnostics.Contracts; |
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Schemas |
||||
|
{ |
||||
|
public class NestedField<T> : NestedField, IField<T> where T : FieldProperties, new() |
||||
|
{ |
||||
|
private T properties; |
||||
|
|
||||
|
public T Properties |
||||
|
{ |
||||
|
get { return properties; } |
||||
|
} |
||||
|
|
||||
|
public override FieldProperties RawProperties |
||||
|
{ |
||||
|
get { return properties; } |
||||
|
} |
||||
|
|
||||
|
public NestedField(long id, string name, T properties) |
||||
|
: base(id, name) |
||||
|
{ |
||||
|
Guard.NotNull(properties, nameof(properties)); |
||||
|
|
||||
|
SetProperties(properties); |
||||
|
} |
||||
|
|
||||
|
[Pure] |
||||
|
public override NestedField Update(FieldProperties newProperties) |
||||
|
{ |
||||
|
var typedProperties = ValidateProperties(newProperties); |
||||
|
|
||||
|
return Clone<NestedField<T>>(clone => |
||||
|
{ |
||||
|
clone.SetProperties(typedProperties); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private void SetProperties(T newProperties) |
||||
|
{ |
||||
|
properties = newProperties; |
||||
|
properties.Freeze(); |
||||
|
} |
||||
|
|
||||
|
private T ValidateProperties(FieldProperties newProperties) |
||||
|
{ |
||||
|
Guard.NotNull(newProperties, nameof(newProperties)); |
||||
|
|
||||
|
if (!(newProperties is T typedProperties)) |
||||
|
{ |
||||
|
throw new ArgumentException($"Properties must be of type '{typeof(T)}", nameof(newProperties)); |
||||
|
} |
||||
|
|
||||
|
return typedProperties; |
||||
|
} |
||||
|
|
||||
|
public override TResult Accept<TResult>(IFieldVisitor<TResult> visitor) |
||||
|
{ |
||||
|
return properties.Accept(visitor, this); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,16 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Newtonsoft.Json.Linq; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.ConvertContent |
||||
|
{ |
||||
|
public static class Value |
||||
|
{ |
||||
|
public static readonly JToken Unset = JValue.CreateUndefined(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,81 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Text; |
||||
|
using Newtonsoft.Json.Linq; |
||||
|
using Squidex.Domain.Apps.Core.Schemas; |
||||
|
using Squidex.Domain.Apps.Core.ValidateContent; |
||||
|
using Squidex.Infrastructure.Json; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.ConvertContent |
||||
|
{ |
||||
|
public delegate JToken ValueConverter(JToken value, IField field); |
||||
|
|
||||
|
public static class ValueConverters |
||||
|
{ |
||||
|
public static ValueConverter DecodeJson() |
||||
|
{ |
||||
|
return (value, field) => |
||||
|
{ |
||||
|
if (!value.IsNull() && field is IField<JsonFieldProperties>) |
||||
|
{ |
||||
|
var decoded = Encoding.UTF8.GetString(Convert.FromBase64String(value.ToString())); |
||||
|
|
||||
|
return JToken.Parse(decoded); |
||||
|
} |
||||
|
|
||||
|
return value; |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
public static ValueConverter EncodeJson() |
||||
|
{ |
||||
|
return (value, field) => |
||||
|
{ |
||||
|
if (!value.IsNull() && field is IField<JsonFieldProperties>) |
||||
|
{ |
||||
|
var encoded = Convert.ToBase64String(Encoding.UTF8.GetBytes(value.ToString())); |
||||
|
|
||||
|
return encoded; |
||||
|
} |
||||
|
|
||||
|
return value; |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
public static ValueConverter ExcludeHidden() |
||||
|
{ |
||||
|
return (value, field) => |
||||
|
{ |
||||
|
return field.IsHidden ? Value.Unset : value; |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
public static ValueConverter ExcludeChangedTypes() |
||||
|
{ |
||||
|
return (value, field) => |
||||
|
{ |
||||
|
if (value.IsNull()) |
||||
|
{ |
||||
|
return value; |
||||
|
} |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
JsonValueConverter.ConvertValue(field, value); |
||||
|
} |
||||
|
catch |
||||
|
{ |
||||
|
return Value.Unset; |
||||
|
} |
||||
|
|
||||
|
return value; |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,69 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using Newtonsoft.Json.Linq; |
||||
|
using Squidex.Domain.Apps.Core.Schemas; |
||||
|
using Squidex.Infrastructure.Json; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.ExtractReferenceIds |
||||
|
{ |
||||
|
public static class ReferencesExtensions |
||||
|
{ |
||||
|
public static IEnumerable<Guid> ExtractReferences(this IField field, JToken value) |
||||
|
{ |
||||
|
return ReferencesExtractor.ExtractReferences(field, value); |
||||
|
} |
||||
|
|
||||
|
public static JToken CleanReferences(this IField field, JToken value, ICollection<Guid> oldReferences) |
||||
|
{ |
||||
|
if (value.IsNull()) |
||||
|
{ |
||||
|
return value; |
||||
|
} |
||||
|
|
||||
|
return ReferencesCleaner.CleanReferences(field, value, oldReferences); |
||||
|
} |
||||
|
|
||||
|
public static JToken ToJToken(this HashSet<Guid> ids) |
||||
|
{ |
||||
|
var result = new JArray(); |
||||
|
|
||||
|
foreach (var id in ids) |
||||
|
{ |
||||
|
result.Add(new JValue(id)); |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
public static HashSet<Guid> ToGuidSet(this JToken value) |
||||
|
{ |
||||
|
if (value is JArray ids) |
||||
|
{ |
||||
|
var result = new HashSet<Guid>(); |
||||
|
|
||||
|
foreach (var id in ids) |
||||
|
{ |
||||
|
if (id.Type == JTokenType.Guid) |
||||
|
{ |
||||
|
result.Add((Guid)id); |
||||
|
} |
||||
|
else if (id.Type == JTokenType.String && Guid.TryParse((string)id, out var guid)) |
||||
|
{ |
||||
|
result.Add(guid); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
return new HashSet<Guid>(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,57 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System; |
|
||||
using System.Collections.Concurrent; |
|
||||
using System.Threading.Tasks; |
|
||||
using Newtonsoft.Json.Linq; |
|
||||
using Squidex.Domain.Apps.Core.Schemas; |
|
||||
using Squidex.Infrastructure; |
|
||||
using Squidex.Infrastructure.Json; |
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Core.ValidateContent |
|
||||
{ |
|
||||
public static class FieldExtensions |
|
||||
{ |
|
||||
public static void AddError(this ConcurrentBag<ValidationError> errors, string message, IField field, IFieldPartitionItem partitionItem = null) |
|
||||
{ |
|
||||
AddError(errors, message, !string.IsNullOrWhiteSpace(field.RawProperties.Label) ? field.RawProperties.Label : field.Name, field.Name, partitionItem); |
|
||||
} |
|
||||
|
|
||||
public static void AddError(this ConcurrentBag<ValidationError> errors, string message, string fieldName, IFieldPartitionItem partitionItem = null) |
|
||||
{ |
|
||||
AddError(errors, message, fieldName, fieldName, partitionItem); |
|
||||
} |
|
||||
|
|
||||
public static void AddError(this ConcurrentBag<ValidationError> errors, string message, string displayName, string fieldName, IFieldPartitionItem partitionItem = null) |
|
||||
{ |
|
||||
if (partitionItem != null && partitionItem != InvariantPartitioning.Instance.Master) |
|
||||
{ |
|
||||
displayName += $" ({partitionItem.Key})"; |
|
||||
} |
|
||||
|
|
||||
errors.Add(new ValidationError(message.Replace("<FIELD>", displayName), fieldName)); |
|
||||
} |
|
||||
|
|
||||
public static async Task ValidateAsync(this IField field, JToken value, ValidationContext context, Action<string> addError) |
|
||||
{ |
|
||||
try |
|
||||
{ |
|
||||
var typedValue = value.IsNull() ? null : JsonValueConverter.ConvertValue(field, value); |
|
||||
|
|
||||
foreach (var validator in ValidatorsFactory.CreateValidators(field)) |
|
||||
{ |
|
||||
await validator.ValidateAsync(typedValue, context, addError); |
|
||||
} |
|
||||
} |
|
||||
catch |
|
||||
{ |
|
||||
addError("<FIELD> is not a valid value."); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,52 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Text; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.ValidateContent |
||||
|
{ |
||||
|
public static class ObjectPath |
||||
|
{ |
||||
|
public static string ToPathString(this IEnumerable<string> path) |
||||
|
{ |
||||
|
var sb = new StringBuilder(); |
||||
|
|
||||
|
var index = 0; |
||||
|
foreach (var property in path) |
||||
|
{ |
||||
|
if (index == 0) |
||||
|
{ |
||||
|
sb.Append(property); |
||||
|
} |
||||
|
else if (index == 1) |
||||
|
{ |
||||
|
if (!property.Equals(InvariantPartitioning.Instance.Master.Key, StringComparison.OrdinalIgnoreCase)) |
||||
|
{ |
||||
|
sb.Append("("); |
||||
|
sb.Append(property); |
||||
|
sb.Append(")"); |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
if (property[0] != '[') |
||||
|
{ |
||||
|
sb.Append("."); |
||||
|
} |
||||
|
|
||||
|
sb.Append(property); |
||||
|
} |
||||
|
|
||||
|
index++; |
||||
|
} |
||||
|
|
||||
|
return sb.ToString(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,53 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
using Newtonsoft.Json.Linq; |
||||
|
using Squidex.Domain.Apps.Core.Schemas; |
||||
|
using Squidex.Infrastructure.Json; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.ValidateContent.Validators |
||||
|
{ |
||||
|
public sealed class FieldValidator : IValidator |
||||
|
{ |
||||
|
private readonly IValidator[] validators; |
||||
|
private readonly IField field; |
||||
|
|
||||
|
public FieldValidator(IValidator[] validators, IField field) |
||||
|
{ |
||||
|
this.validators = validators; |
||||
|
this.field = field; |
||||
|
} |
||||
|
|
||||
|
public async Task ValidateAsync(object value, ValidationContext context, AddError addError) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
object typedValue = null; |
||||
|
|
||||
|
if (value is JToken jToken) |
||||
|
{ |
||||
|
typedValue = jToken.IsNull() ? null : JsonValueConverter.ConvertValue(field, jToken); |
||||
|
} |
||||
|
|
||||
|
var tasks = new List<Task>(); |
||||
|
|
||||
|
foreach (var validator in ValidatorsFactory.CreateValidators(field)) |
||||
|
{ |
||||
|
tasks.Add(validator.ValidateAsync(typedValue, context, addError)); |
||||
|
} |
||||
|
|
||||
|
await Task.WhenAll(tasks); |
||||
|
} |
||||
|
catch |
||||
|
{ |
||||
|
addError(context.Path, "Not a valid value."); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,68 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.ValidateContent.Validators |
||||
|
{ |
||||
|
public sealed class ObjectValidator<TValue> : IValidator |
||||
|
{ |
||||
|
private readonly IDictionary<string, (bool IsOptional, IValidator Validator)> schema; |
||||
|
private readonly bool isPartial; |
||||
|
private readonly string fieldType; |
||||
|
private readonly TValue fieldDefault; |
||||
|
|
||||
|
public ObjectValidator(IDictionary<string, (bool IsOptional, IValidator Validator)> schema, bool isPartial, string fieldType, TValue fieldDefault) |
||||
|
{ |
||||
|
this.schema = schema; |
||||
|
this.fieldDefault = fieldDefault; |
||||
|
this.fieldType = fieldType; |
||||
|
this.isPartial = isPartial; |
||||
|
} |
||||
|
|
||||
|
public async Task ValidateAsync(object value, ValidationContext context, AddError addError) |
||||
|
{ |
||||
|
if (value is IDictionary<string, TValue> values) |
||||
|
{ |
||||
|
foreach (var fieldData in values) |
||||
|
{ |
||||
|
var name = fieldData.Key; |
||||
|
|
||||
|
if (!schema.ContainsKey(name)) |
||||
|
{ |
||||
|
addError(context.Path.Enqueue(name), $"Not a known {fieldType}."); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
var tasks = new List<Task>(); |
||||
|
|
||||
|
foreach (var field in schema) |
||||
|
{ |
||||
|
var name = field.Key; |
||||
|
|
||||
|
if (!values.TryGetValue(name, out var fieldValue)) |
||||
|
{ |
||||
|
if (isPartial) |
||||
|
{ |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
fieldValue = fieldDefault; |
||||
|
} |
||||
|
|
||||
|
var (isOptional, validator) = field.Value; |
||||
|
var fieldContext = context.Nested(name).Optional(isOptional); |
||||
|
|
||||
|
tasks.Add(validator.ValidateAsync(fieldValue, fieldContext, addError)); |
||||
|
} |
||||
|
|
||||
|
await Task.WhenAll(tasks); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,344 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System; |
|
||||
using System.Collections.Generic; |
|
||||
using System.Threading.Tasks; |
|
||||
using GraphQL; |
|
||||
using GraphQL.Resolvers; |
|
||||
using GraphQL.Types; |
|
||||
using Newtonsoft.Json.Linq; |
|
||||
using Squidex.Domain.Apps.Core.Contents; |
|
||||
using Squidex.Domain.Apps.Entities.Contents.Commands; |
|
||||
using Squidex.Domain.Apps.Entities.Schemas; |
|
||||
using Squidex.Infrastructure; |
|
||||
using Squidex.Infrastructure.Commands; |
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types |
|
||||
{ |
|
||||
public sealed class AppMutationsGraphType : ObjectGraphType |
|
||||
{ |
|
||||
public AppMutationsGraphType(IGraphModel model, IEnumerable<ISchemaEntity> schemas) |
|
||||
{ |
|
||||
foreach (var schema in schemas) |
|
||||
{ |
|
||||
var schemaId = schema.NamedId(); |
|
||||
var schemaType = schema.TypeName(); |
|
||||
var schemaName = schema.DisplayName(); |
|
||||
|
|
||||
var contentType = model.GetContentType(schema.Id); |
|
||||
var contentDataType = model.GetContentDataType(schema.Id); |
|
||||
|
|
||||
var resultType = new ContentDataChangedResultGraphType(schemaType, schemaName, contentDataType); |
|
||||
|
|
||||
var inputType = new ContentDataGraphInputType(model, schema); |
|
||||
|
|
||||
AddContentCreate(schemaId, schemaType, schemaName, inputType, contentDataType, contentType); |
|
||||
AddContentUpdate(schemaType, schemaName, inputType, resultType); |
|
||||
AddContentPatch(schemaType, schemaName, inputType, resultType); |
|
||||
AddContentPublish(schemaType, schemaName); |
|
||||
AddContentUnpublish(schemaType, schemaName); |
|
||||
AddContentArchive(schemaType, schemaName); |
|
||||
AddContentRestore(schemaType, schemaName); |
|
||||
AddContentDelete(schemaType, schemaName); |
|
||||
} |
|
||||
|
|
||||
Description = "The app mutations."; |
|
||||
} |
|
||||
|
|
||||
private void AddContentCreate(NamedId<Guid> schemaId, string schemaType, string schemaName, ContentDataGraphInputType inputType, IGraphType contentDataType, IGraphType contentType) |
|
||||
{ |
|
||||
AddField(new FieldType |
|
||||
{ |
|
||||
Name = $"create{schemaType}Content", |
|
||||
Arguments = new QueryArguments |
|
||||
{ |
|
||||
new QueryArgument(AllTypes.None) |
|
||||
{ |
|
||||
Name = "data", |
|
||||
Description = $"The data for the {schemaName} content.", |
|
||||
DefaultValue = null, |
|
||||
ResolvedType = new NonNullGraphType(inputType), |
|
||||
}, |
|
||||
new QueryArgument(AllTypes.None) |
|
||||
{ |
|
||||
Name = "publish", |
|
||||
Description = "Set to true to autopublish content.", |
|
||||
DefaultValue = false, |
|
||||
ResolvedType = AllTypes.Boolean |
|
||||
}, |
|
||||
new QueryArgument(AllTypes.None) |
|
||||
{ |
|
||||
Name = "expectedVersion", |
|
||||
Description = "The expected version", |
|
||||
DefaultValue = EtagVersion.Any, |
|
||||
ResolvedType = AllTypes.Int |
|
||||
} |
|
||||
}, |
|
||||
ResolvedType = new NonNullGraphType(contentType), |
|
||||
Resolver = ResolveAsync(async (c, publish) => |
|
||||
{ |
|
||||
var argPublish = c.GetArgument<bool>("publish"); |
|
||||
|
|
||||
var contentData = GetContentData(c); |
|
||||
|
|
||||
var command = new CreateContent { SchemaId = schemaId, Data = contentData, Publish = argPublish }; |
|
||||
var commandContext = await publish(command); |
|
||||
|
|
||||
var result = commandContext.Result<EntityCreatedResult<NamedContentData>>(); |
|
||||
var response = ContentEntity.Create(command, result); |
|
||||
|
|
||||
return (IContentEntity)ContentEntity.Create(command, result); |
|
||||
}), |
|
||||
Description = $"Creates an {schemaName} content." |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
private void AddContentUpdate(string schemaType, string schemaName, ContentDataGraphInputType inputType, IGraphType resultType) |
|
||||
{ |
|
||||
AddField(new FieldType |
|
||||
{ |
|
||||
Name = $"update{schemaType}Content", |
|
||||
Arguments = new QueryArguments |
|
||||
{ |
|
||||
new QueryArgument(AllTypes.None) |
|
||||
{ |
|
||||
Name = "id", |
|
||||
Description = $"The id of the {schemaName} content (GUID)", |
|
||||
DefaultValue = string.Empty, |
|
||||
ResolvedType = AllTypes.NonNullGuid |
|
||||
}, |
|
||||
new QueryArgument(AllTypes.None) |
|
||||
{ |
|
||||
Name = "data", |
|
||||
Description = $"The data for the {schemaName} content.", |
|
||||
DefaultValue = null, |
|
||||
ResolvedType = new NonNullGraphType(inputType), |
|
||||
}, |
|
||||
new QueryArgument(AllTypes.None) |
|
||||
{ |
|
||||
Name = "expectedVersion", |
|
||||
Description = "The expected version", |
|
||||
DefaultValue = EtagVersion.Any, |
|
||||
ResolvedType = AllTypes.Int |
|
||||
} |
|
||||
}, |
|
||||
ResolvedType = new NonNullGraphType(resultType), |
|
||||
Resolver = ResolveAsync(async (c, publish) => |
|
||||
{ |
|
||||
var contentId = c.GetArgument<Guid>("id"); |
|
||||
var contentData = GetContentData(c); |
|
||||
|
|
||||
var command = new UpdateContent { ContentId = contentId, Data = contentData }; |
|
||||
var commandContext = await publish(command); |
|
||||
|
|
||||
var result = commandContext.Result<ContentDataChangedResult>(); |
|
||||
|
|
||||
return result; |
|
||||
}), |
|
||||
Description = $"Update an {schemaName} content by id." |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
private void AddContentPatch(string schemaType, string schemaName, ContentDataGraphInputType inputType, IGraphType resultType) |
|
||||
{ |
|
||||
AddField(new FieldType |
|
||||
{ |
|
||||
Name = $"patch{schemaType}Content", |
|
||||
Arguments = new QueryArguments |
|
||||
{ |
|
||||
new QueryArgument(AllTypes.None) |
|
||||
{ |
|
||||
Name = "id", |
|
||||
Description = $"The id of the {schemaName} content (GUID)", |
|
||||
DefaultValue = string.Empty, |
|
||||
ResolvedType = AllTypes.NonNullGuid |
|
||||
}, |
|
||||
new QueryArgument(AllTypes.None) |
|
||||
{ |
|
||||
Name = "data", |
|
||||
Description = $"The data for the {schemaName} content.", |
|
||||
DefaultValue = null, |
|
||||
ResolvedType = new NonNullGraphType(inputType), |
|
||||
}, |
|
||||
new QueryArgument(AllTypes.None) |
|
||||
{ |
|
||||
Name = "expectedVersion", |
|
||||
Description = "The expected version", |
|
||||
DefaultValue = EtagVersion.Any, |
|
||||
ResolvedType = AllTypes.Int |
|
||||
} |
|
||||
}, |
|
||||
ResolvedType = new NonNullGraphType(resultType), |
|
||||
Resolver = ResolveAsync(async (c, publish) => |
|
||||
{ |
|
||||
var contentId = c.GetArgument<Guid>("id"); |
|
||||
var contentData = GetContentData(c); |
|
||||
|
|
||||
var command = new PatchContent { ContentId = contentId, Data = contentData }; |
|
||||
var commandContext = await publish(command); |
|
||||
|
|
||||
var result = commandContext.Result<ContentDataChangedResult>(); |
|
||||
|
|
||||
return result; |
|
||||
}), |
|
||||
Description = $"Patch a {schemaName} content." |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
private void AddContentPublish(string schemaType, string schemaName) |
|
||||
{ |
|
||||
AddField(new FieldType |
|
||||
{ |
|
||||
Name = $"publish{schemaType}Content", |
|
||||
Arguments = CreateIdArguments(schemaName), |
|
||||
ResolvedType = AllTypes.CommandVersion, |
|
||||
Resolver = ResolveAsync((c, publish) => |
|
||||
{ |
|
||||
var contentId = c.GetArgument<Guid>("id"); |
|
||||
|
|
||||
var command = new ChangeContentStatus { ContentId = contentId, Status = Status.Published }; |
|
||||
|
|
||||
return publish(command); |
|
||||
}), |
|
||||
Description = $"Publish a {schemaName} content." |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
private void AddContentUnpublish(string schemaType, string schemaName) |
|
||||
{ |
|
||||
AddField(new FieldType |
|
||||
{ |
|
||||
Name = $"unpublish{schemaType}Content", |
|
||||
Arguments = CreateIdArguments(schemaName), |
|
||||
ResolvedType = AllTypes.CommandVersion, |
|
||||
Resolver = ResolveAsync((c, publish) => |
|
||||
{ |
|
||||
var contentId = c.GetArgument<Guid>("id"); |
|
||||
|
|
||||
var command = new ChangeContentStatus { ContentId = contentId, Status = Status.Draft }; |
|
||||
|
|
||||
return publish(command); |
|
||||
}), |
|
||||
Description = $"Unpublish a {schemaName} content." |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
private void AddContentArchive(string schemaType, string schemaName) |
|
||||
{ |
|
||||
AddField(new FieldType |
|
||||
{ |
|
||||
Name = $"archive{schemaType}Content", |
|
||||
Arguments = CreateIdArguments(schemaName), |
|
||||
ResolvedType = AllTypes.CommandVersion, |
|
||||
Resolver = ResolveAsync((c, publish) => |
|
||||
{ |
|
||||
var contentId = c.GetArgument<Guid>("id"); |
|
||||
|
|
||||
var command = new ChangeContentStatus { ContentId = contentId, Status = Status.Archived }; |
|
||||
|
|
||||
return publish(command); |
|
||||
}), |
|
||||
Description = $"Archive a {schemaName} content." |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
private void AddContentRestore(string schemaType, string schemaName) |
|
||||
{ |
|
||||
AddField(new FieldType |
|
||||
{ |
|
||||
Name = $"restore{schemaType}Content", |
|
||||
Arguments = CreateIdArguments(schemaName), |
|
||||
ResolvedType = AllTypes.CommandVersion, |
|
||||
Resolver = ResolveAsync((c, publish) => |
|
||||
{ |
|
||||
var contentId = c.GetArgument<Guid>("id"); |
|
||||
|
|
||||
var command = new ChangeContentStatus { ContentId = contentId, Status = Status.Draft }; |
|
||||
|
|
||||
return publish(command); |
|
||||
}), |
|
||||
Description = $"Restore a {schemaName} content." |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
private void AddContentDelete(string schemaType, string schemaName) |
|
||||
{ |
|
||||
AddField(new FieldType |
|
||||
{ |
|
||||
Name = $"delete{schemaType}Content", |
|
||||
Arguments = CreateIdArguments(schemaName), |
|
||||
ResolvedType = AllTypes.CommandVersion, |
|
||||
Resolver = ResolveAsync((c, publish) => |
|
||||
{ |
|
||||
var contentId = c.GetArgument<Guid>("id"); |
|
||||
|
|
||||
var command = new DeleteContent { ContentId = contentId }; |
|
||||
|
|
||||
return publish(command); |
|
||||
}), |
|
||||
Description = $"Delete an {schemaName} content." |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
private static QueryArguments CreateIdArguments(string schemaName) |
|
||||
{ |
|
||||
return new QueryArguments |
|
||||
{ |
|
||||
new QueryArgument(AllTypes.None) |
|
||||
{ |
|
||||
Name = "id", |
|
||||
Description = $"The id of the {schemaName} content (GUID)", |
|
||||
DefaultValue = string.Empty, |
|
||||
ResolvedType = AllTypes.NonNullGuid |
|
||||
}, |
|
||||
new QueryArgument(AllTypes.None) |
|
||||
{ |
|
||||
Name = "expectedVersion", |
|
||||
Description = "The expected version", |
|
||||
DefaultValue = EtagVersion.Any, |
|
||||
ResolvedType = AllTypes.Int |
|
||||
} |
|
||||
}; |
|
||||
} |
|
||||
|
|
||||
private static IFieldResolver ResolveAsync<T>(Func<ResolveFieldContext, Func<SquidexCommand, Task<CommandContext>>, Task<T>> action) |
|
||||
{ |
|
||||
return new FuncFieldResolver<Task<T>>(async c => |
|
||||
{ |
|
||||
var e = (GraphQLExecutionContext)c.UserContext; |
|
||||
|
|
||||
try |
|
||||
{ |
|
||||
return await action(c, command => |
|
||||
{ |
|
||||
command.ExpectedVersion = c.GetArgument("expectedVersion", EtagVersion.Any); |
|
||||
|
|
||||
return e.CommandBus.PublishAsync(command); |
|
||||
}); |
|
||||
} |
|
||||
catch (ValidationException ex) |
|
||||
{ |
|
||||
c.Errors.Add(new ExecutionError(ex.Message)); |
|
||||
|
|
||||
throw; |
|
||||
} |
|
||||
catch (DomainException ex) |
|
||||
{ |
|
||||
c.Errors.Add(new ExecutionError(ex.Message)); |
|
||||
|
|
||||
throw; |
|
||||
} |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
private static NamedContentData GetContentData(ResolveFieldContext c) |
|
||||
{ |
|
||||
return JObject.FromObject(c.GetArgument<object>("data")).ToObject<NamedContentData>(); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,44 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using GraphQL.Resolvers; |
|
||||
using GraphQL.Types; |
|
||||
using Squidex.Infrastructure.Commands; |
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types |
|
||||
{ |
|
||||
public sealed class CommandVersionGraphType : ObjectGraphType<CommandContext> |
|
||||
{ |
|
||||
public CommandVersionGraphType() |
|
||||
{ |
|
||||
Name = "CommandVersionDto"; |
|
||||
|
|
||||
AddField(new FieldType |
|
||||
{ |
|
||||
Name = "version", |
|
||||
ResolvedType = AllTypes.Int, |
|
||||
Resolver = ResolveVersion(), |
|
||||
Description = "The new version of the item." |
|
||||
}); |
|
||||
|
|
||||
Description = "The result of a mutation"; |
|
||||
} |
|
||||
|
|
||||
private static IFieldResolver ResolveVersion() |
|
||||
{ |
|
||||
return new FuncFieldResolver<CommandContext, int?>(x => |
|
||||
{ |
|
||||
if (x.Source.Result<object>() is EntitySavedResult result) |
|
||||
{ |
|
||||
return (int)result.Version; |
|
||||
} |
|
||||
|
|
||||
return null; |
|
||||
}); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,44 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System; |
|
||||
using GraphQL.Resolvers; |
|
||||
using GraphQL.Types; |
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types |
|
||||
{ |
|
||||
public sealed class ContentDataChangedResultGraphType : ObjectGraphType<ContentDataChangedResult> |
|
||||
{ |
|
||||
public ContentDataChangedResultGraphType(string schemaType, string schemaName, IGraphType contentDataType) |
|
||||
{ |
|
||||
Name = $"{schemaName}DataChangedResultDto"; |
|
||||
|
|
||||
AddField(new FieldType |
|
||||
{ |
|
||||
Name = "version", |
|
||||
ResolvedType = AllTypes.Int, |
|
||||
Resolver = Resolve(x => x.Version), |
|
||||
Description = $"The new version of the {schemaName} content." |
|
||||
}); |
|
||||
|
|
||||
AddField(new FieldType |
|
||||
{ |
|
||||
Name = "data", |
|
||||
ResolvedType = new NonNullGraphType(contentDataType), |
|
||||
Resolver = Resolve(x => x.Data), |
|
||||
Description = $"The new data of the {schemaName} content." |
|
||||
}); |
|
||||
|
|
||||
Description = $"The result of the {schemaName} mutation"; |
|
||||
} |
|
||||
|
|
||||
private static IFieldResolver Resolve(Func<ContentDataChangedResult, object> action) |
|
||||
{ |
|
||||
return new FuncFieldResolver<ContentDataChangedResult, object>(c => action(c.Source)); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,74 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System.Linq; |
|
||||
using GraphQL.Resolvers; |
|
||||
using GraphQL.Types; |
|
||||
using Squidex.Domain.Apps.Core.Contents; |
|
||||
using Squidex.Domain.Apps.Entities.Schemas; |
|
||||
using Squidex.Infrastructure; |
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types |
|
||||
{ |
|
||||
public sealed class ContentDataGraphInputType : InputObjectGraphType |
|
||||
{ |
|
||||
public ContentDataGraphInputType(IGraphModel model, ISchemaEntity schema) |
|
||||
{ |
|
||||
var schemaType = schema.TypeName(); |
|
||||
var schemaName = schema.DisplayName(); |
|
||||
|
|
||||
Name = $"{schemaType}InputDto"; |
|
||||
|
|
||||
foreach (var field in schema.SchemaDef.Fields.Where(x => !x.IsHidden)) |
|
||||
{ |
|
||||
var inputType = model.GetInputGraphType(field); |
|
||||
|
|
||||
if (inputType != null) |
|
||||
{ |
|
||||
if (field.RawProperties.IsRequired) |
|
||||
{ |
|
||||
inputType = new NonNullGraphType(inputType); |
|
||||
} |
|
||||
|
|
||||
var fieldName = field.RawProperties.Label.WithFallback(field.Name); |
|
||||
|
|
||||
var fieldGraphType = new InputObjectGraphType |
|
||||
{ |
|
||||
Name = $"{schemaType}Data{field.Name.ToPascalCase()}InputDto" |
|
||||
}; |
|
||||
|
|
||||
var partition = model.ResolvePartition(field.Partitioning); |
|
||||
|
|
||||
foreach (var partitionItem in partition) |
|
||||
{ |
|
||||
fieldGraphType.AddField(new FieldType |
|
||||
{ |
|
||||
Name = partitionItem.Key, |
|
||||
ResolvedType = inputType, |
|
||||
Resolver = null, |
|
||||
Description = field.RawProperties.Hints |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
fieldGraphType.Description = $"The input structure of the {fieldName} of a {schemaName} content type."; |
|
||||
|
|
||||
var fieldResolver = new FuncFieldResolver<NamedContentData, ContentFieldData>(c => c.Source.GetOrDefault(field.Name)); |
|
||||
|
|
||||
AddField(new FieldType |
|
||||
{ |
|
||||
Name = field.Name.ToCamelCase(), |
|
||||
Resolver = fieldResolver, |
|
||||
ResolvedType = fieldGraphType, |
|
||||
Description = $"The {fieldName} field." |
|
||||
}); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
Description = $"The structure of a {schemaName} content type."; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,31 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using GraphQL.Types; |
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types |
|
||||
{ |
|
||||
public sealed class GeolocationInputGraphType : InputObjectGraphType |
|
||||
{ |
|
||||
public GeolocationInputGraphType() |
|
||||
{ |
|
||||
Name = "GeolocationInputDto"; |
|
||||
|
|
||||
AddField(new FieldType |
|
||||
{ |
|
||||
Name = "latitude", |
|
||||
ResolvedType = AllTypes.NonNullFloat |
|
||||
}); |
|
||||
|
|
||||
AddField(new FieldType |
|
||||
{ |
|
||||
Name = "longitude", |
|
||||
ResolvedType = AllTypes.NonNullFloat |
|
||||
}); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,66 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using GraphQL.Types; |
|
||||
using Squidex.Domain.Apps.Core.Schemas; |
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types |
|
||||
{ |
|
||||
public sealed class InputFieldVisitor : IFieldVisitor<IGraphType> |
|
||||
{ |
|
||||
public static readonly InputFieldVisitor Default = new InputFieldVisitor(); |
|
||||
|
|
||||
private InputFieldVisitor() |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
public IGraphType Visit(IField<AssetsFieldProperties> field) |
|
||||
{ |
|
||||
return AllTypes.ListOfNonNullGuid; |
|
||||
} |
|
||||
|
|
||||
public IGraphType Visit(IField<BooleanFieldProperties> field) |
|
||||
{ |
|
||||
return AllTypes.Boolean; |
|
||||
} |
|
||||
|
|
||||
public IGraphType Visit(IField<DateTimeFieldProperties> field) |
|
||||
{ |
|
||||
return AllTypes.Date; |
|
||||
} |
|
||||
|
|
||||
public IGraphType Visit(IField<GeolocationFieldProperties> field) |
|
||||
{ |
|
||||
return AllTypes.GeolocationInput; |
|
||||
} |
|
||||
|
|
||||
public IGraphType Visit(IField<JsonFieldProperties> field) |
|
||||
{ |
|
||||
return AllTypes.NoopJson; |
|
||||
} |
|
||||
|
|
||||
public IGraphType Visit(IField<NumberFieldProperties> field) |
|
||||
{ |
|
||||
return AllTypes.Float; |
|
||||
} |
|
||||
|
|
||||
public IGraphType Visit(IField<ReferencesFieldProperties> field) |
|
||||
{ |
|
||||
return AllTypes.ListOfNonNullGuid; |
|
||||
} |
|
||||
|
|
||||
public IGraphType Visit(IField<StringFieldProperties> field) |
|
||||
{ |
|
||||
return AllTypes.String; |
|
||||
} |
|
||||
|
|
||||
public IGraphType Visit(IField<TagsFieldProperties> field) |
|
||||
{ |
|
||||
return AllTypes.ListOfNonNullString; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,61 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Linq; |
||||
|
using GraphQL.Resolvers; |
||||
|
using GraphQL.Types; |
||||
|
using Newtonsoft.Json.Linq; |
||||
|
using Squidex.Domain.Apps.Core.Schemas; |
||||
|
using Squidex.Domain.Apps.Entities.Schemas; |
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types |
||||
|
{ |
||||
|
public sealed class NestedGraphType : ObjectGraphType<JObject> |
||||
|
{ |
||||
|
public NestedGraphType(IGraphModel model, ISchemaEntity schema, IArrayField field) |
||||
|
{ |
||||
|
var schemaType = schema.TypeName(); |
||||
|
var schemaName = schema.DisplayName(); |
||||
|
|
||||
|
var fieldType = field.TypeName(); |
||||
|
var fieldName = field.DisplayName(); |
||||
|
|
||||
|
Name = $"{schemaType}{fieldName}ChildDto"; |
||||
|
|
||||
|
foreach (var nestedField in field.Fields.Where(x => !x.IsHidden)) |
||||
|
{ |
||||
|
var fieldInfo = model.GetGraphType(schema, nestedField); |
||||
|
|
||||
|
if (fieldInfo.ResolveType != null) |
||||
|
{ |
||||
|
var resolver = new FuncFieldResolver<object>(c => |
||||
|
{ |
||||
|
if (((JObject)c.Source).TryGetValue(nestedField.Name, out var value)) |
||||
|
{ |
||||
|
return fieldInfo.Resolver(value, c); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
return fieldInfo; |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
AddField(new FieldType |
||||
|
{ |
||||
|
Name = nestedField.Name.ToCamelCase(), |
||||
|
Resolver = resolver, |
||||
|
ResolvedType = fieldInfo.ResolveType, |
||||
|
Description = $"The {fieldName}/{nestedField.DisplayName()} nested field." |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
Description = $"The structure of the {schemaName}.{fieldName} nested schema."; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,32 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using GraphQL.Language.AST; |
||||
|
using GraphQL.Types; |
||||
|
using Newtonsoft.Json.Linq; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Utils |
||||
|
{ |
||||
|
public sealed class JsonConverter : IAstFromValueConverter |
||||
|
{ |
||||
|
public static readonly JsonConverter Instance = new JsonConverter(); |
||||
|
|
||||
|
private JsonConverter() |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public IValue Convert(object value, IGraphType type) |
||||
|
{ |
||||
|
return new JsonValue(value as JObject); |
||||
|
} |
||||
|
|
||||
|
public bool Matches(object value, IGraphType type) |
||||
|
{ |
||||
|
return value is JObject; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,42 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using GraphQL.Language.AST; |
||||
|
using GraphQL.Types; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Utils |
||||
|
{ |
||||
|
public sealed class JsonGraphType : ScalarGraphType |
||||
|
{ |
||||
|
public JsonGraphType() |
||||
|
{ |
||||
|
Name = "Json"; |
||||
|
|
||||
|
Description = "Unstructured Json object"; |
||||
|
} |
||||
|
|
||||
|
public override object Serialize(object value) |
||||
|
{ |
||||
|
return value; |
||||
|
} |
||||
|
|
||||
|
public override object ParseValue(object value) |
||||
|
{ |
||||
|
return value; |
||||
|
} |
||||
|
|
||||
|
public override object ParseLiteral(IValue value) |
||||
|
{ |
||||
|
if (value is JsonValue jsonGraphType) |
||||
|
{ |
||||
|
return jsonGraphType.Value; |
||||
|
} |
||||
|
|
||||
|
return value; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,25 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using GraphQL.Language.AST; |
||||
|
using Newtonsoft.Json.Linq; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Utils |
||||
|
{ |
||||
|
public sealed class JsonValue : ValueNode<JObject> |
||||
|
{ |
||||
|
public JsonValue(JObject value) |
||||
|
{ |
||||
|
Value = value; |
||||
|
} |
||||
|
|
||||
|
protected override bool Equals(ValueNode<JObject> node) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,22 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Squidex.Domain.Apps.Core.Schemas; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Schemas.Commands |
||||
|
{ |
||||
|
public sealed class CreateSchemaNestedField |
||||
|
{ |
||||
|
public string Name { get; set; } |
||||
|
|
||||
|
public bool IsHidden { get; set; } |
||||
|
|
||||
|
public bool IsDisabled { get; set; } |
||||
|
|
||||
|
public FieldProperties Properties { get; set; } |
||||
|
} |
||||
|
} |
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue