mirror of https://github.com/Squidex/squidex.git
48 changed files with 716 additions and 287 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> |
|||
{ |
|||
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,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,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,16 @@ |
|||
// ==========================================================================
|
|||
// 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 |
|||
{ |
|||
bool IsLocked { get; } |
|||
|
|||
Partitioning Partitioning { get; } |
|||
} |
|||
} |
|||
@ -0,0 +1,91 @@ |
|||
// ==========================================================================
|
|||
// 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; |
|||
|
|||
public long Id |
|||
{ |
|||
get { return fieldId; } |
|||
} |
|||
|
|||
public string Name |
|||
{ |
|||
get { return fieldName; } |
|||
} |
|||
|
|||
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 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); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue