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