diff --git a/Dockerfile b/Dockerfile index c948abd84..cbcaac61c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,7 +27,7 @@ RUN dotnet restore COPY backend . # Test Backend -RUN dotnet test --no-restore --filter Category!=Dependencies -v n +RUN dotnet test --no-restore --filter Category!=Dependencies # Publish RUN dotnet publish --no-restore src/Squidex/Squidex.csproj --output /build/ --configuration Release -p:version=$SQUIDEX__VERSION diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldNames.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldNames.cs index 1df458c5e..4bab0e67f 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldNames.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldNames.cs @@ -15,7 +15,7 @@ namespace Squidex.Domain.Apps.Core.Schemas { private static readonly List EmptyNames = new List(); - public static readonly FieldNames Empty = new FieldNames(new List()); + public static readonly FieldNames Empty = new FieldNames(EmptyNames); public FieldNames(params string[] fields) : base(fields?.ToList() ?? EmptyNames) diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldRule.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldRule.cs new file mode 100644 index 000000000..52e08eb43 --- /dev/null +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldRule.cs @@ -0,0 +1,43 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Squidex.Infrastructure; + +namespace Squidex.Domain.Apps.Core.Schemas +{ + [Equals(DoNotAddEqualityOperators = true)] + public sealed class FieldRule + { + public FieldRuleAction Action { get; } + + public string Field { get; } + + public string? Condition { get; } + + public FieldRule(FieldRuleAction action, string field, string? condition) + { + Guard.Enum(action, nameof(action)); + Guard.NotNullOrEmpty(field, nameof(field)); + + Action = action; + + Field = field; + + Condition = condition; + } + + public static FieldRule Disable(string field, string? condition = null) + { + return new FieldRule(FieldRuleAction.Disable, field, condition); + } + + public static FieldRule Hide(string field, string? condition = null) + { + return new FieldRule(FieldRuleAction.Hide, field, condition); + } + } +} diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldRuleAction.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldRuleAction.cs new file mode 100644 index 000000000..8018ced32 --- /dev/null +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldRuleAction.cs @@ -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 enum FieldRuleAction + { + Disable, + Hide, + Require + } +} \ No newline at end of file diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldRules.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldRules.cs new file mode 100644 index 000000000..b7bae685d --- /dev/null +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldRules.cs @@ -0,0 +1,30 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; + +namespace Squidex.Domain.Apps.Core.Schemas +{ + public sealed class FieldRules : ReadOnlyCollection + { + private static readonly List EmptyRules = new List(); + + public static readonly FieldRules Empty = new FieldRules(EmptyRules); + + public FieldRules(params FieldRule[] fields) + : base(fields?.ToList() ?? EmptyRules) + { + } + + public FieldRules(IList list) + : base(list ?? EmptyRules) + { + } + } +} diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/Json/JsonSchemaModel.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/Json/JsonSchemaModel.cs index 70137af90..feddfd84e 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/Json/JsonSchemaModel.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/Json/JsonSchemaModel.cs @@ -42,6 +42,9 @@ namespace Squidex.Domain.Apps.Core.Schemas.Json [JsonProperty] public FieldNames? FieldsInReferences { get; set; } + [JsonProperty] + public FieldRules? FieldRules { get; set; } + [JsonProperty] public JsonFieldModel[] Fields { get; set; } @@ -118,6 +121,11 @@ namespace Squidex.Domain.Apps.Core.Schemas.Json schema = schema.SetFieldsInReferences(FieldsInReferences); } + if (FieldRules?.Count > 0) + { + schema = schema.SetFieldRules(FieldRules); + } + if (PreviewUrls?.Count > 0) { schema = schema.SetPreviewUrls(PreviewUrls); diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/Schema.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/Schema.cs index 54cd45cc6..a87032c59 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/Schema.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/Schema.cs @@ -21,6 +21,7 @@ namespace Squidex.Domain.Apps.Core.Schemas private string category; private FieldNames fieldsInLists = FieldNames.Empty; private FieldNames fieldsInReferences = FieldNames.Empty; + private FieldRules fieldRules = FieldRules.Empty; private FieldCollection fields = FieldCollection.Empty; private IReadOnlyDictionary previewUrls = EmptyPreviewUrls; private SchemaScripts scripts = SchemaScripts.Empty; @@ -72,6 +73,11 @@ namespace Squidex.Domain.Apps.Core.Schemas get { return fields; } } + public FieldRules FieldRules + { + get { return fieldRules; } + } + public FieldNames FieldsInLists { get { return fieldsInLists; } @@ -192,6 +198,28 @@ namespace Squidex.Domain.Apps.Core.Schemas return SetFieldsInReferences(new FieldNames(names)); } + [Pure] + public Schema SetFieldRules(FieldRules rules) + { + rules ??= FieldRules.Empty; + + if (fieldRules.SetEquals(rules)) + { + return this; + } + + return Clone(clone => + { + clone.fieldRules = rules; + }); + } + + [Pure] + public Schema SetFieldRules(params FieldRule[] rules) + { + return SetFieldRules(new FieldRules(rules)); + } + [Pure] public Schema Publish() { diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/EventSynchronization/SchemaSynchronizer.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/EventSynchronization/SchemaSynchronizer.cs index 37cd8387f..bc9d138c4 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/EventSynchronization/SchemaSynchronizer.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/EventSynchronization/SchemaSynchronizer.cs @@ -80,6 +80,11 @@ namespace Squidex.Domain.Apps.Core.EventSynchronization { yield return E(new SchemaUIFieldsConfigured { FieldsInReferences = target.FieldsInReferences }); } + + if (!source.FieldRules.SetEquals(target.FieldRules)) + { + yield return E(new SchemaFieldRulesConfigured { FieldRules = target.FieldRules }); + } } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Text/Lucene/Storage/AssetIndexStorage.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Text/Lucene/Storage/AssetIndexStorage.cs index 24703e09f..2d6fde2c2 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Text/Lucene/Storage/AssetIndexStorage.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Text/Lucene/Storage/AssetIndexStorage.cs @@ -29,9 +29,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.Text.Lucene.Storage this.assetStore = assetStore; } - public async Task CreateDirectoryAsync(Guid schemaId) + public async Task CreateDirectoryAsync(Guid ownerId) { - var directoryInfo = new DirectoryInfo(Path.Combine(Path.GetTempPath(), "LocalIndices", schemaId.ToString())); + var directoryInfo = new DirectoryInfo(Path.Combine(Path.GetTempPath(), "LocalIndices", ownerId.ToString())); if (directoryInfo.Exists) { diff --git a/backend/src/Squidex.Domain.Apps.Entities/Schemas/Commands/ConfigureFieldRules.cs b/backend/src/Squidex.Domain.Apps.Entities/Schemas/Commands/ConfigureFieldRules.cs new file mode 100644 index 000000000..a20df4232 --- /dev/null +++ b/backend/src/Squidex.Domain.Apps.Entities/Schemas/Commands/ConfigureFieldRules.cs @@ -0,0 +1,30 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Collections.Generic; +using System.Linq; +using Squidex.Domain.Apps.Core.Schemas; + +namespace Squidex.Domain.Apps.Entities.Schemas.Commands +{ + public sealed class ConfigureFieldRules : SchemaCommand + { + public List? FieldRules { get; set; } + + public FieldRules ToFieldRules() + { + if (FieldRules?.Count > 0) + { + return new FieldRules(FieldRules.Select(x => x.ToFieldRule()).ToList()); + } + else + { + return Core.Schemas.FieldRules.Empty; + } + } + } +} diff --git a/backend/src/Squidex.Domain.Apps.Entities/Schemas/Commands/FieldRuleCommand.cs b/backend/src/Squidex.Domain.Apps.Entities/Schemas/Commands/FieldRuleCommand.cs new file mode 100644 index 000000000..61deac01f --- /dev/null +++ b/backend/src/Squidex.Domain.Apps.Entities/Schemas/Commands/FieldRuleCommand.cs @@ -0,0 +1,25 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Squidex.Domain.Apps.Core.Schemas; + +namespace Squidex.Domain.Apps.Entities.Schemas.Commands +{ + public sealed class FieldRuleCommand + { + public FieldRuleAction Action { get; set; } + + public string Field { get; set; } + + public string? Condition { get; set; } + + public FieldRule ToFieldRule() + { + return new FieldRule(Action, Field, Condition); + } + } +} diff --git a/backend/src/Squidex.Domain.Apps.Entities/Schemas/Commands/UpsertCommand.cs b/backend/src/Squidex.Domain.Apps.Entities/Schemas/Commands/UpsertCommand.cs index 1dfcc95d2..00489c0d5 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Schemas/Commands/UpsertCommand.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Schemas/Commands/UpsertCommand.cs @@ -6,8 +6,10 @@ // ========================================================================== using System.Collections.Generic; +using System.Linq; using Squidex.Domain.Apps.Core; using Squidex.Domain.Apps.Core.Schemas; +using FieldRules = System.Collections.Generic.List; using SchemaFields = System.Collections.Generic.List; namespace Squidex.Domain.Apps.Entities.Schemas.Commands @@ -24,6 +26,8 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Commands public FieldNames? FieldsInLists { get; set; } + public FieldRules? FieldRules { get; set; } + public SchemaScripts? Scripts { get; set; } public SchemaProperties Properties { get; set; } @@ -59,6 +63,11 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Commands schema = schema.SetFieldsInReferences(FieldsInReferences); } + if (FieldRules != null) + { + schema = schema.SetFieldRules(FieldRules.Select(x => x.ToFieldRule()).ToArray()); + } + if (!string.IsNullOrWhiteSpace(Category)) { schema = schema.ChangeCategory(Category); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchema.cs b/backend/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchema.cs index 5ac8e78b5..f90c38201 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchema.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchema.cs @@ -99,6 +99,16 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards }); } + public static void CanConfigureFieldRules(ConfigureFieldRules command) + { + Guard.NotNull(command, nameof(command)); + + Validate.It(() => "Cannot configure field rules.", e => + { + ValidateFieldRules(command.FieldRules, nameof(command.FieldRules), e); + }); + } + public static void CanPublish(PublishSchema command) { Guard.NotNull(command, nameof(command)); @@ -155,6 +165,8 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards ValidateFieldNames(command, command.FieldsInLists, nameof(command.FieldsInLists), e, IsMetaField); ValidateFieldNames(command, command.FieldsInReferences, nameof(command.FieldsInReferences), e, IsNotAllowed); + + ValidateFieldRules(command.FieldRules, nameof(command.FieldRules), e); } private static void ValidateRootField(UpsertSchemaField field, string prefix, AddValidation e) @@ -290,6 +302,31 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards } } + private static void ValidateFieldRules(List? fieldRules, string path, AddValidation e) + { + if (fieldRules != null) + { + var ruleIndex = 0; + var rulePrefix = string.Empty; + + foreach (var fieldRule in fieldRules) + { + ruleIndex++; + rulePrefix = $"{path}[{ruleIndex}]"; + + if (string.IsNullOrWhiteSpace(fieldRule.Field)) + { + e(Not.Defined(nameof(fieldRule.Field)), $"{rulePrefix}.{nameof(fieldRule.Field)}"); + } + + if (!fieldRule.Action.IsEnumValue()) + { + e(Not.Valid(nameof(fieldRule.Action)), $"{rulePrefix}.{nameof(fieldRule.Action)}"); + } + } + } + } + private static void ValidateFieldNames(UpsertCommand command, FieldNames? fields, string path, AddValidation e, Func isAllowed) { if (fields != null) diff --git a/backend/src/Squidex.Domain.Apps.Entities/Schemas/SchemaDomainObject.cs b/backend/src/Squidex.Domain.Apps.Entities/Schemas/SchemaDomainObject.cs index 24d9659f9..efd84de50 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Schemas/SchemaDomainObject.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Schemas/SchemaDomainObject.cs @@ -188,6 +188,16 @@ namespace Squidex.Domain.Apps.Entities.Schemas return Snapshot; }); + case ConfigureFieldRules configureFieldRules: + return UpdateReturn(configureFieldRules, c => + { + GuardSchema.CanConfigureFieldRules(c); + + ConfigureFieldRules(c); + + return Snapshot; + }); + case ConfigureScripts configureScripts: return UpdateReturn(configureScripts, c => { @@ -325,6 +335,11 @@ namespace Squidex.Domain.Apps.Entities.Schemas RaiseEvent(command, new SchemaScriptsConfigured()); } + public void ConfigureFieldRules(ConfigureFieldRules command) + { + RaiseEvent(command, new SchemaFieldRulesConfigured { FieldRules = command.ToFieldRules() }); + } + public void ChangeCategory(ChangeCategory command) { RaiseEvent(command, new SchemaCategoryChanged()); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Schemas/State/SchemaState.cs b/backend/src/Squidex.Domain.Apps.Entities/Schemas/State/SchemaState.cs index 06e141d75..65879c991 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Schemas/State/SchemaState.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Schemas/State/SchemaState.cs @@ -101,6 +101,13 @@ namespace Squidex.Domain.Apps.Entities.Schemas.State break; } + case SchemaFieldRulesConfigured e: + { + SchemaDef = SchemaDef.SetFieldRules(e.FieldRules); + + break; + } + case SchemaPublished _: { SchemaDef = SchemaDef.Publish(); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Squidex.Domain.Apps.Entities.csproj b/backend/src/Squidex.Domain.Apps.Entities/Squidex.Domain.Apps.Entities.csproj index e8dc8cf01..a2a8a8d80 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Squidex.Domain.Apps.Entities.csproj +++ b/backend/src/Squidex.Domain.Apps.Entities/Squidex.Domain.Apps.Entities.csproj @@ -20,6 +20,7 @@ + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/backend/src/Squidex.Domain.Apps.Events/Schemas/SchemaFieldRulesConfigured.cs b/backend/src/Squidex.Domain.Apps.Events/Schemas/SchemaFieldRulesConfigured.cs new file mode 100644 index 000000000..177d291d4 --- /dev/null +++ b/backend/src/Squidex.Domain.Apps.Events/Schemas/SchemaFieldRulesConfigured.cs @@ -0,0 +1,18 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Infrastructure.EventSourcing; + +namespace Squidex.Domain.Apps.Events.Schemas +{ + [EventType(nameof(SchemaFieldRulesConfigured))] + public sealed class SchemaFieldRulesConfigured : SchemaEvent + { + public FieldRules FieldRules { get; set; } + } +} diff --git a/backend/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj b/backend/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj index 6cd1437b6..9cb38209d 100644 --- a/backend/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj +++ b/backend/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj @@ -11,6 +11,7 @@ + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/backend/src/Squidex.Infrastructure/Validation/CustomValidators.cs b/backend/src/Squidex.Infrastructure/Validation/CustomValidators.cs new file mode 100644 index 000000000..0416133e8 --- /dev/null +++ b/backend/src/Squidex.Infrastructure/Validation/CustomValidators.cs @@ -0,0 +1,19 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Squidex.Infrastructure; + +namespace FluentValidation +{ + public static class CustomValidators + { + public static IRuleBuilderOptions Slug(this IRuleBuilder ruleBuilder) + { + return ruleBuilder.Must(x => x.IsSlug()).WithMessage("{PropertyName} must be a valid slug."); + } + } +} diff --git a/backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/ConfigureFieldRulesDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/ConfigureFieldRulesDto.cs new file mode 100644 index 000000000..3619877f7 --- /dev/null +++ b/backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/ConfigureFieldRulesDto.cs @@ -0,0 +1,29 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Collections.Generic; +using System.Linq; +using Squidex.Domain.Apps.Entities.Schemas.Commands; + +namespace Squidex.Areas.Api.Controllers.Schemas.Models +{ + public sealed class ConfigureFieldRulesDto + { + /// + /// The field rules to configure. + /// + public List? FieldRules { get; set; } + + public ConfigureFieldRules ToCommand() + { + return new ConfigureFieldRules + { + FieldRules = FieldRules?.Select(x => x.ToCommand()).ToList() + }; + } + } +} diff --git a/backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/FieldRuleDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/FieldRuleDto.cs new file mode 100644 index 000000000..f1dcb191e --- /dev/null +++ b/backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/FieldRuleDto.cs @@ -0,0 +1,44 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.ComponentModel.DataAnnotations; +using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Domain.Apps.Entities.Schemas.Commands; +using Squidex.Infrastructure.Reflection; + +namespace Squidex.Areas.Api.Controllers.Schemas.Models +{ + public sealed class FieldRuleDto + { + /// + /// The action to perform when the condition is met. + /// + [Required] + public FieldRuleAction Action { get; set; } + + /// + /// The field to update. + /// + [Required] + public string Field { get; set; } + + /// + /// The condition. + /// + public string? Condition { get; set; } + + public static FieldRuleDto FromFieldRule(FieldRule fieldRule) + { + return SimpleMapper.Map(fieldRule, new FieldRuleDto()); + } + + public FieldRuleCommand ToCommand() + { + return SimpleMapper.Map(this, new FieldRuleCommand()); + } + } +} diff --git a/backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDetailsDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDetailsDto.cs index dc9439fa3..2643a14fa 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDetailsDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDetailsDto.cs @@ -42,6 +42,11 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models [Required] public List FieldsInReferences { get; set; } + /// + /// The field rules. + /// + public List FieldRules { get; set; } + /// /// The list of fields. /// @@ -58,9 +63,10 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models SimpleMapper.Map(schema.SchemaDef.Properties, result.Properties); result.FieldsInLists = schema.SchemaDef.FieldsInLists.ToList(); - result.FieldsInReferences = schema.SchemaDef.FieldsInReferences.ToList(); + result.FieldRules = schema.SchemaDef.FieldRules.Select(FieldRuleDto.FromFieldRule).ToList(); + if (schema.SchemaDef.PreviewUrls.Count > 0) { result.PreviewUrls = new Dictionary(schema.SchemaDef.PreviewUrls); diff --git a/backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDto.cs index 5dd4cd33c..a833c76bf 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDto.cs @@ -133,6 +133,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models AddPutLink("update", resources.Url(x => nameof(x.PutSchema), values)); AddPutLink("update/sync", resources.Url(x => nameof(x.PutSchemaSync), values)); AddPutLink("update/urls", resources.Url(x => nameof(x.PutPreviewUrls), values)); + AddPutLink("update/rules", resources.Url(x => nameof(x.PutRules), values)); AddPutLink("update/category", resources.Url(x => nameof(x.PutCategory), values)); } diff --git a/backend/src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs b/backend/src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs index b331b1bfe..640d087b0 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs @@ -208,6 +208,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas /// The preview urls for the schema. /// /// 200 => Schema updated. + /// 400 => Schema urls are not valid. /// 404 => Schema or app not found. /// [HttpPut] @@ -232,7 +233,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas /// The schema scripts object that needs to updated. /// /// 200 => Schema updated. - /// 400 => Schema properties are not valid. + /// 400 => Schema scripts are not valid. /// 404 => Schema or app not found. /// [HttpPut] @@ -249,6 +250,31 @@ namespace Squidex.Areas.Api.Controllers.Schemas return Ok(response); } + /// + /// Update the rules. + /// + /// The name of the app. + /// The name of the schema. + /// The schema rules object that needs to updated. + /// + /// 200 => Schema updated. + /// 400 => Schema rules are not valid. + /// 404 => Schema or app not found. + /// + [HttpPut] + [Route("apps/{app}/schemas/{name}/rules/")] + [ProducesResponseType(typeof(SchemaDetailsDto), 200)] + [ApiPermissionOrAnonymous(Permissions.AppSchemasUpdate)] + [ApiCosts(1)] + public async Task PutRules(string app, string name, [FromBody] ConfigureFieldRulesDto request) + { + var command = request.ToCommand(); + + var response = await InvokeCommandAsync(app, command); + + return Ok(response); + } + /// /// Publish a schema. /// diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/SchemaTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/SchemaTests.cs index 274b8559e..cf1ddb0d6 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/SchemaTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/SchemaTests.cs @@ -397,6 +397,17 @@ namespace Squidex.Domain.Apps.Core.Model.Schemas Assert.NotSame(schema_1, schema_2); } + [Fact] + public void Should_set_field_rules() + { + var schema_1 = schema_0.SetFieldRules(FieldRule.Hide("2")); + var schema_2 = schema_1.SetFieldRules(FieldRule.Hide("2")); + + Assert.NotEmpty(schema_1.FieldRules); + Assert.NotEmpty(schema_2.FieldRules); + Assert.Same(schema_1, schema_2); + } + [Fact] public void Should_set_scripts() { @@ -447,6 +458,7 @@ namespace Squidex.Domain.Apps.Core.Model.Schemas var schemaSource = TestUtils.MixedSchema(true) .ChangeCategory("Category") + .SetFieldRules(FieldRule.Hide("2")) .SetFieldsInLists("field2") .SetFieldsInReferences("field1") .SetPreviewUrls(new Dictionary diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/EventSynchronization/SchemaSynchronizerTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/EventSynchronization/SchemaSynchronizerTests.cs index ed8ab0d7f..3e57ade43 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/EventSynchronization/SchemaSynchronizerTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/EventSynchronization/SchemaSynchronizerTests.cs @@ -174,6 +174,24 @@ namespace Squidex.Domain.Apps.Core.Operations.EventSynchronization ); } + [Fact] + public void Should_create_events_if_field_rules_changed_changed() + { + var sourceSchema = + new Schema("source") + .SetFieldRules(FieldRule.Hide("2")); + + var targetSchema = + new Schema("target") + .SetFieldRules(FieldRule.Hide("1")); + + var events = sourceSchema.Synchronize(targetSchema, idGenerator); + + events.ShouldHaveSameEvents( + new SchemaFieldRulesConfigured { FieldRules = new FieldRules(FieldRule.Hide("1")) } + ); + } + [Fact] public void Should_create_events_if_nested_field_deleted() { diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/GuardSchemaTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/GuardSchemaTests.cs index 1b077b24f..c3a9e4663 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/GuardSchemaTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/GuardSchemaTests.cs @@ -569,6 +569,51 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards GuardSchema.CanConfigureUIFields(command, schema_0); } + [Fact] + public void CanConfigureFieldRules_should_throw_exception_if_field_rules_are_invalid() + { + var command = new ConfigureFieldRules + { + FieldRules = new List + { + new FieldRuleCommand { Field = "field", Action = (FieldRuleAction)5 }, + new FieldRuleCommand(), + } + }; + + ValidationAssert.Throws(() => GuardSchema.CanConfigureFieldRules(command), + new ValidationError("Action is not a valid value.", + "FieldRules[1].Action"), + new ValidationError("Field is required.", + "FieldRules[2].Field")); + } + + [Fact] + public void CanConfigureFieldRules_should_not_throw_exception_if_field_rules_are_valid() + { + var command = new ConfigureFieldRules + { + FieldRules = new List + { + new FieldRuleCommand { Field = "field1", Action = FieldRuleAction.Disable, Condition = "a == b" }, + new FieldRuleCommand { Field = "field2" } + } + }; + + GuardSchema.CanConfigureFieldRules(command); + } + + [Fact] + public void CanConfigureFieldRules_should_not_throw_exception_if_field_rules_are_null() + { + var command = new ConfigureFieldRules + { + FieldRules = null + }; + + GuardSchema.CanConfigureFieldRules(command); + } + [Fact] public void CanPublish_should_not_throw_exception() { diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/SchemaDomainObjectTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/SchemaDomainObjectTests.cs index e9e08d053..d9950b497 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/SchemaDomainObjectTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/SchemaDomainObjectTests.cs @@ -148,12 +148,39 @@ namespace Squidex.Domain.Apps.Entities.Schemas result.ShouldBeEquivalent(sut.Snapshot); + Assert.Equal("", sut.Snapshot.SchemaDef.Scripts.Query); + LastEvents .ShouldHaveSameEvents( CreateEvent(new SchemaScriptsConfigured { Scripts = command.Scripts }) ); } + [Fact] + public async Task ConfigureFieldRules_should_create_events_and_update_schema_field_rules() + { + var command = new ConfigureFieldRules + { + FieldRules = new List + { + new FieldRuleCommand { Field = "field1" } + } + }; + + await ExecuteCreateAsync(); + + var result = await PublishIdempotentAsync(command); + + result.ShouldBeEquivalent(sut.Snapshot); + + Assert.NotEmpty(sut.Snapshot.SchemaDef.FieldRules); + + LastEvents + .ShouldHaveSameEvents( + CreateEvent(new SchemaFieldRulesConfigured { FieldRules = new FieldRules(FieldRule.Disable("field1")) }) + ); + } + [Fact] public async Task ConfigureUIFields_should_create_events_for_list_fields_and_update_schema() { diff --git a/frontend/app-config/karma-test-shim.js b/frontend/app-config/karma-test-shim.js index a9627af2f..e0c8bbea6 100644 --- a/frontend/app-config/karma-test-shim.js +++ b/frontend/app-config/karma-test-shim.js @@ -14,6 +14,6 @@ testing.getTestBed().initTestEnvironment( ); // Then we find all the tests. -const context = require.context('./../app', true, /\.spec\.ts$/); +const context = require.context('./../app', true, /contents\.forms\.spec\.ts$/); // And load the modules. context.keys().map(context); \ No newline at end of file diff --git a/frontend/app/features/administration/pages/event-consumers/event-consumers-page.component.ts b/frontend/app/features/administration/pages/event-consumers/event-consumers-page.component.ts index 3c871f8ce..db581d1f1 100644 --- a/frontend/app/features/administration/pages/event-consumers/event-consumers-page.component.ts +++ b/frontend/app/features/administration/pages/event-consumers/event-consumers-page.component.ts @@ -36,7 +36,7 @@ export class EventConsumersPageComponent extends ResourceOwner implements OnInit this.eventConsumersState.load(true, false); } - public trackByEventConsumer(index: number, es: EventConsumerDto) { + public trackByEventConsumer(_index: number, es: EventConsumerDto) { return es.name; } diff --git a/frontend/app/features/administration/pages/users/user-page.component.html b/frontend/app/features/administration/pages/users/user-page.component.html index afaa0a55b..36a6441b9 100644 --- a/frontend/app/features/administration/pages/users/user-page.component.html +++ b/frontend/app/features/administration/pages/users/user-page.component.html @@ -43,14 +43,14 @@
- +
- +
@@ -59,7 +59,7 @@
- +
@@ -67,7 +67,7 @@
- +
@@ -76,7 +76,7 @@
- +
diff --git a/frontend/app/features/administration/pages/users/users-page.component.ts b/frontend/app/features/administration/pages/users/users-page.component.ts index be99a19cb..e45532772 100644 --- a/frontend/app/features/administration/pages/users/users-page.component.ts +++ b/frontend/app/features/administration/pages/users/users-page.component.ts @@ -44,7 +44,7 @@ export class UsersPageComponent extends ResourceOwner implements OnInit { this.usersState.search(this.usersFilter.value); } - public trackByUser(index: number, user: UserDto) { + public trackByUser(_ndex: number, user: UserDto) { return user.id; } } \ No newline at end of file diff --git a/frontend/app/features/apps/pages/apps-page.component.ts b/frontend/app/features/apps/pages/apps-page.component.ts index 4b889156a..4396e14cb 100644 --- a/frontend/app/features/apps/pages/apps-page.component.ts +++ b/frontend/app/features/apps/pages/apps-page.component.ts @@ -70,7 +70,7 @@ export class AppsPageComponent implements OnInit { this.addAppDialog.show(); } - public trackByApp(index: number, app: AppDto) { + public trackByApp(_index: number, app: AppDto) { return app.id; } } \ No newline at end of file diff --git a/frontend/app/features/apps/pages/news-dialog.component.ts b/frontend/app/features/apps/pages/news-dialog.component.ts index 001477bdd..bbb796bad 100644 --- a/frontend/app/features/apps/pages/news-dialog.component.ts +++ b/frontend/app/features/apps/pages/news-dialog.component.ts @@ -20,7 +20,7 @@ export class NewsDialogComponent { @Input() public features: ReadonlyArray; - public trackByFeature(index: number, feature: FeatureDto) { + public trackByFeature(_index: number, feature: FeatureDto) { return feature; } } \ No newline at end of file diff --git a/frontend/app/features/assets/pages/asset-tags.component.ts b/frontend/app/features/assets/pages/asset-tags.component.ts index c2868e822..76756985d 100644 --- a/frontend/app/features/assets/pages/asset-tags.component.ts +++ b/frontend/app/features/assets/pages/asset-tags.component.ts @@ -35,7 +35,7 @@ export class AssetTagsComponent { return this.tagsSelected[tag.name] === true; } - public trackByTag(index: number, tag: Tag) { + public trackByTag(_index: number, tag: Tag) { return tag.name; } } \ No newline at end of file diff --git a/frontend/app/features/assets/pages/assets-filters-page.component.ts b/frontend/app/features/assets/pages/assets-filters-page.component.ts index c1e067904..bcd9c5f05 100644 --- a/frontend/app/features/assets/pages/assets-filters-page.component.ts +++ b/frontend/app/features/assets/pages/assets-filters-page.component.ts @@ -38,7 +38,7 @@ export class AssetsFiltersPageComponent { this.assetsState.resetTags(); } - public trackByTag(index: number, tag: { name: string }) { + public trackByTag(_index: number, tag: { name: string }) { return tag.name; } } \ No newline at end of file diff --git a/frontend/app/features/content/declarations.ts b/frontend/app/features/content/declarations.ts index 1b1fee9df..2245c5082 100644 --- a/frontend/app/features/content/declarations.ts +++ b/frontend/app/features/content/declarations.ts @@ -24,7 +24,6 @@ export * from './shared/forms/array-section.component'; export * from './shared/forms/assets-editor.component'; export * from './shared/forms/field-editor.component'; export * from './shared/forms/stock-photo-editor.component'; -export * from './shared/group-fields.pipe'; export * from './shared/list/content-list-cell.directive'; export * from './shared/list/content-list-field.component'; export * from './shared/list/content-list-header.component'; diff --git a/frontend/app/features/content/module.ts b/frontend/app/features/content/module.ts index b94c13a98..a16823683 100644 --- a/frontend/app/features/content/module.ts +++ b/frontend/app/features/content/module.ts @@ -10,7 +10,7 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { CanDeactivateGuard, ContentMustExistGuard, LoadLanguagesGuard, SchemaMustExistPublishedGuard, SchemaMustNotBeSingletonGuard, SqxFrameworkModule, SqxSharedModule, UnsetContentGuard } from '@app/shared'; -import { ArrayEditorComponent, ArrayItemComponent, ArraySectionComponent, AssetsEditorComponent, CommentsPageComponent, ContentComponent, ContentCreatorComponent, ContentEventComponent, ContentFieldComponent, ContentHistoryPageComponent, ContentListCellDirective, ContentListFieldComponent, ContentListHeaderComponent, ContentListWidthPipe, ContentPageComponent, ContentSectionComponent, ContentSelectorComponent, ContentSelectorItemComponent, ContentsFiltersPageComponent, ContentsPageComponent, ContentStatusComponent, ContentValueComponent, ContentValueEditorComponent, CustomViewEditorComponent, DueTimeSelectorComponent, FieldEditorComponent, FieldLanguagesComponent, GroupFieldsPipe, PreviewButtonComponent, ReferenceItemComponent, ReferencesEditorComponent, SchemasPageComponent, StockPhotoEditorComponent } from './declarations'; +import { ArrayEditorComponent, ArrayItemComponent, ArraySectionComponent, AssetsEditorComponent, CommentsPageComponent, ContentComponent, ContentCreatorComponent, ContentEventComponent, ContentFieldComponent, ContentHistoryPageComponent, ContentListCellDirective, ContentListFieldComponent, ContentListHeaderComponent, ContentListWidthPipe, ContentPageComponent, ContentSectionComponent, ContentSelectorComponent, ContentSelectorItemComponent, ContentsFiltersPageComponent, ContentsPageComponent, ContentStatusComponent, ContentValueComponent, ContentValueEditorComponent, CustomViewEditorComponent, DueTimeSelectorComponent, FieldEditorComponent, FieldLanguagesComponent, PreviewButtonComponent, ReferenceItemComponent, ReferencesEditorComponent, SchemasPageComponent, StockPhotoEditorComponent } from './declarations'; const routes: Routes = [ { @@ -101,7 +101,6 @@ const routes: Routes = [ DueTimeSelectorComponent, FieldEditorComponent, FieldLanguagesComponent, - GroupFieldsPipe, PreviewButtonComponent, ReferenceItemComponent, ReferencesEditorComponent, diff --git a/frontend/app/features/content/pages/content/content-field.component.html b/frontend/app/features/content/pages/content/content-field.component.html index 75482a695..df7ae8cd6 100644 --- a/frontend/app/features/content/pages/content/content-field.component.html +++ b/frontend/app/features/content/pages/content/content-field.component.html @@ -1,13 +1,13 @@ -
-
-
+
+
+
-
+
@@ -32,10 +31,9 @@ @@ -43,15 +41,15 @@
-
+
-
+
@@ -74,8 +73,7 @@ diff --git a/frontend/app/features/content/pages/content/content-field.component.ts b/frontend/app/features/content/pages/content/content-field.component.ts index 62cf5dcd8..4b466415d 100644 --- a/frontend/app/features/content/pages/content/content-field.component.ts +++ b/frontend/app/features/content/pages/content/content-field.component.ts @@ -6,8 +6,7 @@ */ import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core'; -import { FormGroup } from '@angular/forms'; -import { AppLanguageDto, AppsState, EditContentForm, fieldInvariant, invalid$, LocalStoreService, RootFieldDto, SchemaDto, StringFieldPropertiesDto, TranslationsService, Types, value$ } from '@app/shared'; +import { AppLanguageDto, AppsState, EditContentForm, FieldForm, invalid$, LocalStoreService, SchemaDto, StringFieldPropertiesDto, TranslationsService, Types, value$ } from '@app/shared'; import { Observable } from 'rxjs'; import { combineLatest } from 'rxjs/operators'; @@ -24,16 +23,16 @@ export class ContentFieldComponent implements OnChanges { public form: EditContentForm; @Input() - public formContext: any; + public formCompare?: EditContentForm; @Input() - public field: RootFieldDto; + public formContext: any; @Input() - public fieldForm: FormGroup; + public formModel: FieldForm; @Input() - public fieldFormCompare?: FormGroup; + public formModelCompare?: FieldForm; @Input() public schema: SchemaDto; @@ -54,11 +53,11 @@ export class ContentFieldComponent implements OnChanges { return false; } - if (!this.field.isLocalizable) { + if (!this.formModel.field.isLocalizable) { return false; } - const properties = this.field.properties; + const properties = this.formModel.field.properties; return Types.is(properties, StringFieldPropertiesDto) && (properties.editor === 'Input' || properties.editor === 'TextArea'); } @@ -73,14 +72,14 @@ export class ContentFieldComponent implements OnChanges { public ngOnChanges(changes: SimpleChanges) { this.showAllControls = this.localStore.getBoolean(this.configKey()); - if (changes['fieldForm'] && this.fieldForm) { - this.isInvalid = invalid$(this.fieldForm); + if (changes['formModel'] && this.formModel) { + this.isInvalid = invalid$(this.formModel.form); } - if ((changes['fieldForm'] || changes['fieldFormCompare']) && this.fieldFormCompare) { + if ((changes['formModel'] || changes['formModelCompare']) && this.formModelCompare) { this.isDifferent = - value$(this.fieldForm).pipe( - combineLatest(value$(this.fieldFormCompare), + value$(this.formModel.form).pipe( + combineLatest(value$(this.formModelCompare!.form), (lhs, rhs) => !Types.equals(lhs, rhs, true))); } } @@ -92,11 +91,11 @@ export class ContentFieldComponent implements OnChanges { } public copy() { - if (this.fieldFormCompare && this.fieldFormCompare) { + if (this.formModel && this.formModelCompare) { if (this.showAllControls) { - this.fieldForm.setValue(this.fieldFormCompare.value); + this.formModel.copyAllFrom(this.formModelCompare); } else { - this.getControl()!.setValue(this.getControlCompare()!.value); + this.formModel.copyFrom(this.formModelCompare, this.language.iso2Code); } } } @@ -106,7 +105,7 @@ export class ContentFieldComponent implements OnChanges { if (master) { const masterCode = master.iso2Code; - const masterValue = this.fieldForm.get(masterCode)!.value; + const masterValue = this.formModel.get(masterCode)!.form.value; if (masterValue) { if (this.showAllControls) { @@ -123,10 +122,10 @@ export class ContentFieldComponent implements OnChanges { } private translateValue(text: string, sourceLanguage: string, targetLanguage: string) { - const control = this.fieldForm.get(targetLanguage); + const control = this.formModel.get(targetLanguage); if (control) { - const value = control.value; + const value = control.form.value; if (!value) { const request = { text, sourceLanguage, targetLanguage }; @@ -134,38 +133,30 @@ export class ContentFieldComponent implements OnChanges { this.translations.translate(this.appsState.appName, request) .subscribe(result => { if (result.text) { - control.setValue(result.text); + control.form.setValue(result.text); } }); } } } - private findControl(form?: FormGroup) { - if (this.field.isLocalizable) { - return form?.controls[this.language.iso2Code]; - } else { - return form?.controls[fieldInvariant]; - } - } - public prefix(language: AppLanguageDto) { return `(${language.iso2Code})`; } public getControl() { - return this.findControl(this.fieldForm); + return this.formModel.get(this.language.iso2Code); } public getControlCompare() { - return this.findControl(this.fieldFormCompare); + return this.formModelCompare?.get(this.language.iso2Code); } - public trackByLanguage(index: number, language: AppLanguageDto) { + public trackByLanguage(_index: number, language: AppLanguageDto) { return language.iso2Code; } private configKey() { - return `squidex.schemas.${this.schema?.id}.fields.${this.field?.fieldId}.show-all`; + return `squidex.schemas.${this.schema?.id}.fields.${this.formModel.field.fieldId}.show-all`; } } \ No newline at end of file diff --git a/frontend/app/features/content/pages/content/content-history-page.component.ts b/frontend/app/features/content/pages/content/content-history-page.component.ts index 7c22761d8..be0559ae1 100644 --- a/frontend/app/features/content/pages/content/content-history-page.component.ts +++ b/frontend/app/features/content/pages/content/content-history-page.component.ts @@ -98,7 +98,7 @@ export class ContentHistoryPageComponent extends ResourceOwner implements OnInit this.contentPage.loadVersion(event.version, true); } - public trackByEvent(index: number, event: HistoryEventDto) { + public trackByEvent(_index: number, event: HistoryEventDto) { return event.eventId; } } \ No newline at end of file diff --git a/frontend/app/features/content/pages/content/content-page.component.html b/frontend/app/features/content/pages/content/content-page.component.html index 5ccb728a8..5cb520d36 100644 --- a/frontend/app/features/content/pages/content/content-page.component.html +++ b/frontend/app/features/content/pages/content/content-page.component.html @@ -81,14 +81,14 @@
- + [schema]="schema">
diff --git a/frontend/app/features/content/pages/content/content-page.component.ts b/frontend/app/features/content/pages/content/content-page.component.ts index f79dbe732..141308f4b 100644 --- a/frontend/app/features/content/pages/content/content-page.component.ts +++ b/frontend/app/features/content/pages/content/content-page.component.ts @@ -9,10 +9,9 @@ import { Component, OnInit } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; -import { ApiUrlConfig, AppLanguageDto, AuthService, AutoSaveKey, AutoSaveService, CanComponentDeactivate, ContentDto, ContentsState, DialogService, EditContentForm, fadeAnimation, LanguagesState, ModalModel, ResourceOwner, RootFieldDto, SchemaDetailsDto, SchemasState, TempService, Version } from '@app/shared'; +import { ApiUrlConfig, AppLanguageDto, AuthService, AutoSaveKey, AutoSaveService, CanComponentDeactivate, ContentDto, ContentsState, DialogService, EditContentForm, fadeAnimation, FieldForm, FieldSection, LanguagesState, ModalModel, ResourceOwner, RootFieldDto, SchemaDetailsDto, SchemasState, TempService, valueAll$, Version } from '@app/shared'; import { Observable, of } from 'rxjs'; import { debounceTime, filter, onErrorResumeNext, tap } from 'rxjs/operators'; -import { FieldSection } from '../../shared/group-fields.pipe'; @Component({ selector: 'sqx-content-page', @@ -70,7 +69,7 @@ export class ContentPageComponent extends ResourceOwner implements CanComponentD .subscribe(schema => { this.schema = schema; - this.contentForm = new EditContentForm(this.languages, this.schema); + this.contentForm = new EditContentForm(this.languages, this.schema, this.formContext.user); })); this.own( @@ -111,7 +110,7 @@ export class ContentPageComponent extends ResourceOwner implements CanComponentD })); this.own( - this.contentForm.form.valueChanges.pipe( + valueAll$(this.contentForm.form).pipe( filter(_ => !this.isLoadingContent), filter(_ => this.contentForm.form.enabled), debounceTime(2000) @@ -220,7 +219,7 @@ export class ContentPageComponent extends ResourceOwner implements CanComponentD this.contentsState.loadVersion(content, version) .subscribe(dto => { if (compare) { - this.contentFormCompare = new EditContentForm(this.languages, this.schema); + this.contentFormCompare = new EditContentForm(this.languages, this.schema, this.formContext.user); this.contentFormCompare.load(dto.payload); this.contentFormCompare.setEnabled(false); @@ -250,7 +249,7 @@ export class ContentPageComponent extends ResourceOwner implements CanComponentD } } - public trackBySection(index: number, section: FieldSection) { + public trackBySection(_index: number, section: FieldSection) { return section.separator?.fieldId; } } diff --git a/frontend/app/features/content/pages/content/content-section.component.html b/frontend/app/features/content/pages/content/content-section.component.html index cb6409a25..b62a82228 100644 --- a/frontend/app/features/content/pages/content/content-section.component.html +++ b/frontend/app/features/content/pages/content/content-section.component.html @@ -1,28 +1,30 @@ -
-
-
- -
-
-

{{separator!.displayName}}

+ +
+
+
+ +
+
+

{{separator!.displayName}}

- - {{separator!.properties.hints}} - + + {{separator!.properties.hints}} + +
-
+ -
- + diff --git a/frontend/app/features/content/pages/content/content-section.component.ts b/frontend/app/features/content/pages/content/content-section.component.ts index c181d0fed..6af052635 100644 --- a/frontend/app/features/content/pages/content/content-section.component.ts +++ b/frontend/app/features/content/pages/content/content-section.component.ts @@ -6,8 +6,7 @@ */ import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Output } from '@angular/core'; -import { AppLanguageDto, EditContentForm, LocalStoreService, RootFieldDto, SchemaDto } from '@app/shared'; -import { FieldSection } from './../../shared/group-fields.pipe'; +import { AppLanguageDto, EditContentForm, FieldForm, FieldSection, LocalStoreService, RootFieldDto, SchemaDto } from '@app/shared'; @Component({ selector: 'sqx-content-section', @@ -29,10 +28,10 @@ export class ContentSectionComponent implements OnChanges { public formContext: any; @Input() - public schema: SchemaDto; + public formSection: FieldSection; @Input() - public section: FieldSection; + public schema: SchemaDto; @Input() public language: AppLanguageDto; @@ -57,19 +56,15 @@ export class ContentSectionComponent implements OnChanges { this.localStore.setBoolean(this.configKey(), this.isCollapsed); } - public getFieldForm(field: RootFieldDto) { - return this.form.form.get(field.name)!; - } - - public getFieldFormCompare(field: RootFieldDto) { - return this.formCompare?.form.get(field.name)!; + public getFieldFormCompare(formState: FieldForm) { + return this.formCompare?.get(formState.field.name); } - public trackByField(index: number, field: RootFieldDto) { - return field.fieldId; + public trackByField(_index: number, formState: FieldForm) { + return formState.field.fieldId; } private configKey(): string { - return `squidex.schemas.${this.schema?.id}.fields.${this.section?.separator?.fieldId}.closed`; + return `squidex.schemas.${this.schema?.id}.fields.${this.formSection?.separator?.fieldId}.closed`; } } \ No newline at end of file diff --git a/frontend/app/features/content/pages/contents/contents-page.component.ts b/frontend/app/features/content/pages/contents/contents-page.component.ts index a6d8a4818..f69c41cef 100644 --- a/frontend/app/features/content/pages/contents/contents-page.component.ts +++ b/frontend/app/features/content/pages/contents/contents-page.component.ts @@ -181,7 +181,7 @@ export class ContentsPageComponent extends ResourceOwner implements OnInit { this.updateSelectionSummary(); } - public trackByContent(content: ContentDto): string { + public trackByContent(_index: number, content: ContentDto): string { return content.id; } diff --git a/frontend/app/features/content/pages/schemas/schemas-page.component.ts b/frontend/app/features/content/pages/schemas/schemas-page.component.ts index 356d9c897..85728cfb6 100644 --- a/frontend/app/features/content/pages/schemas/schemas-page.component.ts +++ b/frontend/app/features/content/pages/schemas/schemas-page.component.ts @@ -40,7 +40,7 @@ export class SchemasPageComponent implements OnInit { this.localStore.setBoolean('content.schemas.collapsed', this.isCollapsed); } - public trackByCategory(index: number, category: SchemaCategory) { + public trackByCategory(_index: number, category: SchemaCategory) { return category.name; } } \ No newline at end of file diff --git a/frontend/app/features/content/shared/forms/array-editor.component.html b/frontend/app/features/content/shared/forms/array-editor.component.html index a4e88db22..0b9a3d9bc 100644 --- a/frontend/app/features/content/shared/forms/array-editor.component.html +++ b/frontend/app/features/content/shared/forms/array-editor.component.html @@ -1,26 +1,23 @@ -
-
+ (clone)="itemAdd(itemForm)" (move)="move(itemForm, $event)" (remove)="itemRemove(i)">
@@ -28,12 +25,12 @@
-
-
+
diff --git a/frontend/app/features/content/shared/forms/array-editor.component.ts b/frontend/app/features/content/shared/forms/array-editor.component.ts index 7102d6333..3b7949393 100644 --- a/frontend/app/features/content/shared/forms/array-editor.component.ts +++ b/frontend/app/features/content/shared/forms/array-editor.component.ts @@ -7,8 +7,7 @@ import { CdkDragDrop } from '@angular/cdk/drag-drop'; import { ChangeDetectionStrategy, Component, Input, QueryList, ViewChildren } from '@angular/core'; -import { AbstractControl, FormArray, FormGroup } from '@angular/forms'; -import { AppLanguageDto, EditContentForm, RootFieldDto, sorted } from '@app/shared'; +import { AppLanguageDto, EditContentForm, FieldArrayForm, FieldArrayItemForm, sorted } from '@app/shared'; import { ArrayItemComponent } from './array-item.component'; @Component({ @@ -25,7 +24,7 @@ export class ArrayEditorComponent { public formContext: any; @Input() - public field: RootFieldDto; + public formModel: FieldArrayForm; @Input() public language: AppLanguageDto; @@ -33,22 +32,31 @@ export class ArrayEditorComponent { @Input() public languages: ReadonlyArray; - @Input() - public arrayControl: FormArray; - @ViewChildren(ArrayItemComponent) public children: QueryList; + public get field() { + return this.formModel.field; + } + public itemRemove(index: number) { - this.form.arrayItemRemove(this.field, this.language, index); + this.formModel.removeItemAt(index); } - public itemAdd(value?: FormGroup) { - this.form.arrayItemInsert(this.field, this.language, value); + public itemAdd(value?: FieldArrayItemForm) { + this.formModel.addItem(value); } - public sort(event: CdkDragDrop>) { - this.sortInternal(sorted(event)); + public sort(event: CdkDragDrop>) { + this.formModel.sort(sorted(event)); + + this.reset(); + } + + public move(index: number, item: FieldArrayItemForm) { + this.formModel.move(index, item); + + this.reset(); } public collapseAll() { @@ -68,21 +76,4 @@ export class ArrayEditorComponent { child.reset(); }); } - - public move(control: AbstractControl, index: number) { - const controls = [...this.arrayControl.controls]; - - controls.splice(controls.indexOf(control), 1); - controls.splice(index, 0, control); - - this.sortInternal(controls); - } - - private sortInternal(controls: ReadonlyArray) { - for (let i = 0; i < controls.length; i++) { - this.arrayControl.setControl(i, controls[i]); - } - - this.reset(); - } } \ No newline at end of file diff --git a/frontend/app/features/content/shared/forms/array-item.component.html b/frontend/app/features/content/shared/forms/array-item.component.html index 19432add3..0cc9211fd 100644 --- a/frontend/app/features/content/shared/forms/array-item.component.html +++ b/frontend/app/features/content/shared/forms/array-item.component.html @@ -43,14 +43,13 @@
-
+
+ [languages]="languages">
diff --git a/frontend/app/features/content/shared/forms/array-item.component.ts b/frontend/app/features/content/shared/forms/array-item.component.ts index bb484ecfb..6b64cb489 100644 --- a/frontend/app/features/content/shared/forms/array-item.component.ts +++ b/frontend/app/features/content/shared/forms/array-item.component.ts @@ -6,11 +6,9 @@ */ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, OnChanges, Output, QueryList, SimpleChanges, ViewChildren } from '@angular/core'; -import { FormGroup } from '@angular/forms'; -import { AppLanguageDto, EditContentForm, FieldFormatter, invalid$, NestedFieldDto, RootFieldDto, value$ } from '@app/shared'; +import { AppLanguageDto, EditContentForm, FieldArrayItemForm, FieldArrayItemValueForm, FieldFormatter, FieldSection, invalid$, NestedFieldDto, value$ } from '@app/shared'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; -import { FieldSection } from './../group-fields.pipe'; import { ArraySectionComponent } from './array-section.component'; import { FieldEditorComponent } from './field-editor.component'; @@ -37,7 +35,7 @@ export class ArrayItemComponent implements OnChanges { public formContext: any; @Input() - public field: RootFieldDto; + public formModel: FieldArrayItemForm; @Input() public isFirst = false; @@ -51,9 +49,6 @@ export class ArrayItemComponent implements OnChanges { @Input() public index: number; - @Input() - public itemForm: FormGroup; - @Input() public language: AppLanguageDto; @@ -74,23 +69,21 @@ export class ArrayItemComponent implements OnChanges { } public ngOnChanges(changes: SimpleChanges) { - if (changes['itemForm']) { - this.isInvalid = invalid$(this.itemForm); - } + if (changes['formModel']) { + this.isInvalid = invalid$(this.formModel.form); - if (changes['itemForm'] || changes['field']) { - this.title = value$(this.itemForm).pipe(map(x => this.getTitle(x))); + this.title = value$(this.formModel.form).pipe(map(x => this.getTitle(x))); } } private getTitle(value: any) { const values: string[] = []; - for (const field of this.field.nested) { - const control = this.itemForm.get(field.name); + for (const field of this.formModel.field.nested) { + const fieldValue = value[field.name]; - if (control) { - const formatted = FieldFormatter.format(field, control.value); + if (fieldValue) { + const formatted = FieldFormatter.format(field, fieldValue); if (formatted) { values.push(formatted); @@ -135,7 +128,7 @@ export class ArrayItemComponent implements OnChanges { }); } - public trackBySection(index: number, section: FieldSection) { + public trackBySection(_index: number, section: FieldSection) { return section.separator?.fieldId; } } \ No newline at end of file diff --git a/frontend/app/features/content/shared/forms/array-section.component.html b/frontend/app/features/content/shared/forms/array-section.component.html index 6aea12189..68bd477ed 100644 --- a/frontend/app/features/content/shared/forms/array-section.component.html +++ b/frontend/app/features/content/shared/forms/array-section.component.html @@ -1,18 +1,19 @@ -
-

{{separator!.displayName}}

+ +
+

{{separator!.displayName}}

- - {{separator!.properties.hints}} - -
- -
- - -
\ No newline at end of file + + {{separator!.properties.hints}} + +
+ +
+ + +
+ \ No newline at end of file diff --git a/frontend/app/features/content/shared/forms/array-section.component.ts b/frontend/app/features/content/shared/forms/array-section.component.ts index 8120477fd..e910bf239 100644 --- a/frontend/app/features/content/shared/forms/array-section.component.ts +++ b/frontend/app/features/content/shared/forms/array-section.component.ts @@ -6,9 +6,7 @@ */ import { ChangeDetectionStrategy, Component, Input, QueryList, ViewChildren } from '@angular/core'; -import { FormGroup } from '@angular/forms'; -import { AppLanguageDto, EditContentForm, NestedFieldDto } from '@app/shared'; -import { FieldSection } from './../group-fields.pipe'; +import { AppLanguageDto, EditContentForm, FieldArrayItemForm, FieldSection, NestedFieldDto } from '@app/shared'; import { FieldEditorComponent } from './field-editor.component'; @Component({ @@ -18,9 +16,6 @@ import { FieldEditorComponent } from './field-editor.component'; changeDetection: ChangeDetectionStrategy.OnPush }) export class ArraySectionComponent { - @Input() - public itemForm: FormGroup; - @Input() public form: EditContentForm; @@ -28,28 +23,24 @@ export class ArraySectionComponent { public formContext: any; @Input() - public language: AppLanguageDto; + public formSection: FieldSection; @Input() - public languages: ReadonlyArray; + public language: AppLanguageDto; @Input() - public section: FieldSection; + public languages: ReadonlyArray; @ViewChildren(FieldEditorComponent) public editors: QueryList; - public getControl(field: NestedFieldDto) { - return this.itemForm.get(field.name)!; - } - public reset() { this.editors.forEach(editor => { editor.reset(); }); } - public trackByField(index: number, field: NestedFieldDto) { - return field.fieldId; + public trackByField(_index: number, field: FieldArrayItemForm) { + return field.field.fieldId; } } \ No newline at end of file diff --git a/frontend/app/features/content/shared/forms/assets-editor.component.ts b/frontend/app/features/content/shared/forms/assets-editor.component.ts index abeba63b0..b01dd4344 100644 --- a/frontend/app/features/content/shared/forms/assets-editor.component.ts +++ b/frontend/app/features/content/shared/forms/assets-editor.component.ts @@ -172,7 +172,7 @@ export class AssetsEditorComponent extends StatefulControlComponent +
Disabled - +
@@ -19,11 +19,9 @@ - diff --git a/frontend/app/features/content/shared/forms/field-editor.component.ts b/frontend/app/features/content/shared/forms/field-editor.component.ts index 30e816e38..3b7b294fa 100644 --- a/frontend/app/features/content/shared/forms/field-editor.component.ts +++ b/frontend/app/features/content/shared/forms/field-editor.component.ts @@ -7,7 +7,7 @@ import { Component, ElementRef, Input, OnChanges, SimpleChanges, ViewChild } from '@angular/core'; import { AbstractControl, FormArray, FormControl } from '@angular/forms'; -import { AppLanguageDto, EditContentForm, FieldDto, MathHelper, RootFieldDto, Types } from '@app/shared'; +import { AbstractContentForm, AppLanguageDto, EditContentForm, FieldDto, MathHelper, RootFieldDto, Types } from '@app/shared'; @Component({ selector: 'sqx-field-editor', @@ -22,10 +22,7 @@ export class FieldEditorComponent implements OnChanges { public formContext: any; @Input() - public field: FieldDto; - - @Input() - public control: AbstractControl; + public formModel: AbstractContentForm; @Input() public language: AppLanguageDto; @@ -39,16 +36,20 @@ export class FieldEditorComponent implements OnChanges { @ViewChild('editor', { static: false }) public editor: ElementRef; + public get field() { + return this.formModel.field; + } + public get arrayControl() { - return this.control as FormArray; + return this.formModel.form as FormArray; } public get editorControl() { - return this.control as FormControl; + return this.formModel.form as FormControl; } public get rootField() { - return this.field as RootFieldDto; + return this.formModel.field as RootFieldDto; } public uniqueId = MathHelper.guid(); diff --git a/frontend/app/features/content/shared/forms/stock-photo-editor.component.ts b/frontend/app/features/content/shared/forms/stock-photo-editor.component.ts index a7d8a5a5e..688b0973f 100644 --- a/frontend/app/features/content/shared/forms/stock-photo-editor.component.ts +++ b/frontend/app/features/content/shared/forms/stock-photo-editor.component.ts @@ -112,7 +112,7 @@ export class StockPhotoEditorComponent extends StatefulControlComponent { - separator?: T; - - fields: ReadonlyArray; -} - -@Pipe({ - name: 'sqxGroupFields', - pure: true -}) -export class GroupFieldsPipe implements PipeTransform { - public transform(fields: ReadonlyArray) { - const sections: FieldSection[] = []; - - let currentSeparator: T | undefined = undefined; - let currentFields: T[] = []; - - for (const field of fields) { - if (field.properties.isContentField) { - currentFields.push(field); - } else { - sections.push({ separator: currentSeparator, fields: currentFields }); - - currentFields = []; - currentSeparator = field; - } - } - - if (currentFields.length > 0) { - sections.push({ separator: currentSeparator, fields: currentFields }); - } - - return sections; - } -} \ No newline at end of file diff --git a/frontend/app/features/content/shared/references/content-creator.component.html b/frontend/app/features/content/shared/references/content-creator.component.html index a3983b5a6..91bb9a529 100644 --- a/frontend/app/features/content/shared/references/content-creator.component.html +++ b/frontend/app/features/content/shared/references/content-creator.component.html @@ -40,13 +40,13 @@
- + [schema]="schema">
diff --git a/frontend/app/features/content/shared/references/content-creator.component.ts b/frontend/app/features/content/shared/references/content-creator.component.ts index 1817f8f3e..e73361a1f 100644 --- a/frontend/app/features/content/shared/references/content-creator.component.ts +++ b/frontend/app/features/content/shared/references/content-creator.component.ts @@ -68,7 +68,7 @@ export class ContentCreatorComponent extends ResourceOwner implements OnInit { this.schema = schema; this.contentsState.schema = schema; - this.contentForm = new EditContentForm(this.languages, this.schema); + this.contentForm = new EditContentForm(this.languages, this.schema, this.contentFormContext.user); this.changeDetector.markForCheck(); } diff --git a/frontend/app/features/content/shared/references/content-selector.component.ts b/frontend/app/features/content/shared/references/content-selector.component.ts index cb62e60b1..5543e38bb 100644 --- a/frontend/app/features/content/shared/references/content-selector.component.ts +++ b/frontend/app/features/content/shared/references/content-selector.component.ts @@ -153,7 +153,7 @@ export class ContentSelectorComponent extends ResourceOwner implements OnInit { } } - public trackByContent(index: number, content: ContentDto): string { + public trackByContent(_index: number, content: ContentDto): string { return content.id; } } \ No newline at end of file diff --git a/frontend/app/features/content/shared/references/references-editor.component.ts b/frontend/app/features/content/shared/references/references-editor.component.ts index 7bf2d0b32..89dca7275 100644 --- a/frontend/app/features/content/shared/references/references-editor.component.ts +++ b/frontend/app/features/content/shared/references/references-editor.component.ts @@ -131,7 +131,7 @@ export class ReferencesEditorComponent extends StatefulControlComponent ({ ...s, isCompact })); } - public trackByContent(index: number, content: ContentDto) { + public trackByContent(_index: number, content: ContentDto) { return content.id; } } \ No newline at end of file diff --git a/frontend/app/features/rules/pages/events/rule-events-page.component.ts b/frontend/app/features/rules/pages/events/rule-events-page.component.ts index 82f52bb1b..b03d78e5e 100644 --- a/frontend/app/features/rules/pages/events/rule-events-page.component.ts +++ b/frontend/app/features/rules/pages/events/rule-events-page.component.ts @@ -45,7 +45,7 @@ export class RuleEventsPageComponent implements OnInit { this.selectedEventId = this.selectedEventId !== id ? id : null; } - public trackByRuleEvent(index: number, ruleEvent: RuleEventDto) { + public trackByRuleEvent(_index: number, ruleEvent: RuleEventDto) { return ruleEvent.id; } } \ No newline at end of file diff --git a/frontend/app/features/rules/pages/rules/actions/generic-action.component.html b/frontend/app/features/rules/pages/rules/actions/generic-action.component.html index 0e57104cf..038c5d184 100644 --- a/frontend/app/features/rules/pages/rules/actions/generic-action.component.html +++ b/frontend/app/features/rules/pages/rules/actions/generic-action.component.html @@ -5,7 +5,7 @@
- + diff --git a/frontend/app/features/rules/pages/rules/actions/generic-action.component.ts b/frontend/app/features/rules/pages/rules/actions/generic-action.component.ts index ec8fc4aa7..72e112951 100644 --- a/frontend/app/features/rules/pages/rules/actions/generic-action.component.ts +++ b/frontend/app/features/rules/pages/rules/actions/generic-action.component.ts @@ -24,9 +24,6 @@ export class GenericActionComponent implements OnInit { @Input() public actionForm: FormGroup; - @Input() - public actionFormSubmitted = false; - public ngOnInit() { for (const property of this.definition.properties) { const validators = []; diff --git a/frontend/app/features/rules/pages/rules/rule-wizard.component.html b/frontend/app/features/rules/pages/rules/rule-wizard.component.html index 70390653c..85b6d84ad 100644 --- a/frontend/app/features/rules/pages/rules/rule-wizard.component.html +++ b/frontend/app/features/rules/pages/rules/rule-wizard.component.html @@ -59,37 +59,32 @@ + [triggerForm]="triggerForm.form"> + [triggerForm]="triggerForm.form"> + [triggerForm]="triggerForm.form"> + [triggerForm]="triggerForm.form"> + [triggerForm]="triggerForm.form"> @@ -117,8 +112,7 @@ + [actionForm]="actionForm.form"> diff --git a/frontend/app/features/rules/pages/rules/rules-page.component.ts b/frontend/app/features/rules/pages/rules/rules-page.component.ts index d563a4d1c..498c8781b 100644 --- a/frontend/app/features/rules/pages/rules/rules-page.component.ts +++ b/frontend/app/features/rules/pages/rules/rules-page.component.ts @@ -81,7 +81,7 @@ export class RulesPageComponent implements OnInit { this.addRuleDialog.show(); } - public trackByRule(index: number, rule: RuleDto) { + public trackByRule(_index: number, rule: RuleDto) { return rule.id; } } diff --git a/frontend/app/features/rules/pages/rules/triggers/asset-changed-trigger.component.html b/frontend/app/features/rules/pages/rules/triggers/asset-changed-trigger.component.html index 1ae2b45f6..ef8cac61f 100644 --- a/frontend/app/features/rules/pages/rules/triggers/asset-changed-trigger.component.html +++ b/frontend/app/features/rules/pages/rules/triggers/asset-changed-trigger.component.html @@ -2,7 +2,7 @@
- +
diff --git a/frontend/app/features/rules/pages/rules/triggers/asset-changed-trigger.component.ts b/frontend/app/features/rules/pages/rules/triggers/asset-changed-trigger.component.ts index 2c8138bb3..31959bd99 100644 --- a/frontend/app/features/rules/pages/rules/triggers/asset-changed-trigger.component.ts +++ b/frontend/app/features/rules/pages/rules/triggers/asset-changed-trigger.component.ts @@ -20,9 +20,6 @@ export class AssetChangedTriggerComponent implements OnInit { @Input() public triggerForm: FormGroup; - @Input() - public triggerFormSubmitted = false; - public ngOnInit() { this.triggerForm.setControl('condition', new FormControl(this.trigger.condition || '')); diff --git a/frontend/app/features/rules/pages/rules/triggers/comment-trigger.component.html b/frontend/app/features/rules/pages/rules/triggers/comment-trigger.component.html index b042da2db..7cbf53e6d 100644 --- a/frontend/app/features/rules/pages/rules/triggers/comment-trigger.component.html +++ b/frontend/app/features/rules/pages/rules/triggers/comment-trigger.component.html @@ -2,7 +2,7 @@
- +
diff --git a/frontend/app/features/rules/pages/rules/triggers/comment-trigger.component.ts b/frontend/app/features/rules/pages/rules/triggers/comment-trigger.component.ts index 75d1a8dc9..5a832722d 100644 --- a/frontend/app/features/rules/pages/rules/triggers/comment-trigger.component.ts +++ b/frontend/app/features/rules/pages/rules/triggers/comment-trigger.component.ts @@ -20,9 +20,6 @@ export class CommentTriggerComponent implements OnInit { @Input() public triggerForm: FormGroup; - @Input() - public triggerFormSubmitted = false; - public ngOnInit() { this.triggerForm.setControl('condition', new FormControl(this.trigger.condition || '')); diff --git a/frontend/app/features/rules/pages/rules/triggers/content-changed-trigger.component.ts b/frontend/app/features/rules/pages/rules/triggers/content-changed-trigger.component.ts index 589c1595b..c21e0af5b 100644 --- a/frontend/app/features/rules/pages/rules/triggers/content-changed-trigger.component.ts +++ b/frontend/app/features/rules/pages/rules/triggers/content-changed-trigger.component.ts @@ -30,9 +30,6 @@ export class ContentChangedTriggerComponent implements OnInit { @Input() public triggerForm: FormGroup; - @Input() - public triggerFormSubmitted = false; - public triggerSchemas: ReadonlyArray; public schemaToAdd: SchemaDto; @@ -99,7 +96,7 @@ export class ContentChangedTriggerComponent implements OnInit { this.schemaToAdd = this.schemasToAdd[0]; } - public trackBySchema(index: number, schema: SchemaDto) { + public trackBySchema(_index: number, schema: SchemaDto) { return schema.id; } } \ No newline at end of file diff --git a/frontend/app/features/rules/pages/rules/triggers/schema-changed-trigger.component.html b/frontend/app/features/rules/pages/rules/triggers/schema-changed-trigger.component.html index ff6b78cdf..666ae22f9 100644 --- a/frontend/app/features/rules/pages/rules/triggers/schema-changed-trigger.component.html +++ b/frontend/app/features/rules/pages/rules/triggers/schema-changed-trigger.component.html @@ -2,7 +2,7 @@
- +
diff --git a/frontend/app/features/rules/pages/rules/triggers/schema-changed-trigger.component.ts b/frontend/app/features/rules/pages/rules/triggers/schema-changed-trigger.component.ts index 4096a8410..f087db798 100644 --- a/frontend/app/features/rules/pages/rules/triggers/schema-changed-trigger.component.ts +++ b/frontend/app/features/rules/pages/rules/triggers/schema-changed-trigger.component.ts @@ -20,9 +20,6 @@ export class SchemaChangedTriggerComponent implements OnInit { @Input() public triggerForm: FormGroup; - @Input() - public triggerFormSubmitted = false; - public ngOnInit() { this.triggerForm.setControl('condition', new FormControl(this.trigger.condition || '')); diff --git a/frontend/app/features/rules/pages/rules/triggers/usage-trigger.component.html b/frontend/app/features/rules/pages/rules/triggers/usage-trigger.component.html index 5a0575e7d..e27b580de 100644 --- a/frontend/app/features/rules/pages/rules/triggers/usage-trigger.component.html +++ b/frontend/app/features/rules/pages/rules/triggers/usage-trigger.component.html @@ -2,7 +2,7 @@
- + @@ -14,7 +14,7 @@
- + diff --git a/frontend/app/features/rules/pages/rules/triggers/usage-trigger.component.ts b/frontend/app/features/rules/pages/rules/triggers/usage-trigger.component.ts index b1ae1dc6b..6e4536f4c 100644 --- a/frontend/app/features/rules/pages/rules/triggers/usage-trigger.component.ts +++ b/frontend/app/features/rules/pages/rules/triggers/usage-trigger.component.ts @@ -21,9 +21,6 @@ export class UsageTriggerComponent implements OnInit { @Input() public triggerForm: FormGroup; - @Input() - public triggerFormSubmitted = false; - public ngOnInit() { this.triggerForm.setControl('limit', new FormControl(this.trigger.limit || 20000, [ diff --git a/frontend/app/features/schemas/declarations.ts b/frontend/app/features/schemas/declarations.ts index 69818c416..df2295476 100644 --- a/frontend/app/features/schemas/declarations.ts +++ b/frontend/app/features/schemas/declarations.ts @@ -34,6 +34,7 @@ export * from './pages/schema/fields/types/string-validation.component'; export * from './pages/schema/fields/types/tags-ui.component'; export * from './pages/schema/fields/types/tags-validation.component'; export * from './pages/schema/preview/schema-preview-urls-form.component'; +export * from './pages/schema/rules/schema-field-rules-form.component'; export * from './pages/schema/schema-page.component'; export * from './pages/schema/scripts/schema-scripts-form.component'; export * from './pages/schema/ui/field-list.component'; diff --git a/frontend/app/features/schemas/module.ts b/frontend/app/features/schemas/module.ts index 105e8876d..8fe96046f 100644 --- a/frontend/app/features/schemas/module.ts +++ b/frontend/app/features/schemas/module.ts @@ -10,7 +10,7 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { HelpComponent, SchemaMustExistGuard, SqxFrameworkModule, SqxSharedModule } from '@app/shared'; -import { ArrayValidationComponent, AssetsUIComponent, AssetsValidationComponent, BooleanUIComponent, BooleanValidationComponent, DateTimeUIComponent, DateTimeValidationComponent, FieldComponent, FieldFormCommonComponent, FieldFormComponent, FieldFormUIComponent, FieldFormValidationComponent, FieldListComponent, FieldWizardComponent, GeolocationUIComponent, GeolocationValidationComponent, JsonUIComponent, JsonValidationComponent, NumberUIComponent, NumberValidationComponent, ReferencesUIComponent, ReferencesValidationComponent, SchemaEditFormComponent, SchemaExportFormComponent, SchemaFieldsComponent, SchemaFormComponent, SchemaPageComponent, SchemaPreviewUrlsFormComponent, SchemaScriptsFormComponent, SchemasPageComponent, SchemaUIFormComponent, StringUIComponent, StringValidationComponent, TagsUIComponent, TagsValidationComponent } from './declarations'; +import { ArrayValidationComponent, AssetsUIComponent, AssetsValidationComponent, BooleanUIComponent, BooleanValidationComponent, DateTimeUIComponent, DateTimeValidationComponent, FieldComponent, FieldFormCommonComponent, FieldFormComponent, FieldFormUIComponent, FieldFormValidationComponent, FieldListComponent, FieldWizardComponent, GeolocationUIComponent, GeolocationValidationComponent, JsonUIComponent, JsonValidationComponent, NumberUIComponent, NumberValidationComponent, ReferencesUIComponent, ReferencesValidationComponent, SchemaEditFormComponent, SchemaExportFormComponent, SchemaFieldRulesFormComponent, SchemaFieldsComponent, SchemaFormComponent, SchemaPageComponent, SchemaPreviewUrlsFormComponent, SchemaScriptsFormComponent, SchemasPageComponent, SchemaUIFormComponent, StringUIComponent, StringValidationComponent, TagsUIComponent, TagsValidationComponent } from './declarations'; const routes: Routes = [ { @@ -69,6 +69,7 @@ const routes: Routes = [ ReferencesValidationComponent, SchemaEditFormComponent, SchemaExportFormComponent, + SchemaFieldRulesFormComponent, SchemaFieldsComponent, SchemaFormComponent, SchemaPageComponent, diff --git a/frontend/app/features/schemas/pages/schema/common/schema-edit-form.component.html b/frontend/app/features/schemas/pages/schema/common/schema-edit-form.component.html index 41b80f503..dfedcb8d1 100644 --- a/frontend/app/features/schemas/pages/schema/common/schema-edit-form.component.html +++ b/frontend/app/features/schemas/pages/schema/common/schema-edit-form.component.html @@ -1,5 +1,5 @@ -
+
Common
@@ -13,7 +13,7 @@
- + @@ -23,7 +23,7 @@
- +
@@ -31,7 +31,7 @@
- + diff --git a/frontend/app/features/schemas/pages/schema/common/schema-edit-form.component.ts b/frontend/app/features/schemas/pages/schema/common/schema-edit-form.component.ts index 6f7b556fe..a7b575985 100644 --- a/frontend/app/features/schemas/pages/schema/common/schema-edit-form.component.ts +++ b/frontend/app/features/schemas/pages/schema/common/schema-edit-form.component.ts @@ -20,7 +20,7 @@ export class SchemaEditFormComponent implements OnChanges { @Input() public schema: SchemaDetailsDto; - public editForm = new EditSchemaForm(this.formBuilder); + public fieldForm = new EditSchemaForm(this.formBuilder); public isEditable = false; @@ -33,8 +33,8 @@ export class SchemaEditFormComponent implements OnChanges { public ngOnChanges() { this.isEditable = this.schema.canUpdate; - this.editForm.load(this.schema.properties); - this.editForm.setEnabled(this.isEditable); + this.fieldForm.load(this.schema.properties); + this.fieldForm.setEnabled(this.isEditable); } public saveSchema() { @@ -42,14 +42,14 @@ export class SchemaEditFormComponent implements OnChanges { return; } - const value = this.editForm.submit(); + const value = this.fieldForm.submit(); if (value) { this.schemasState.update(this.schema, value) .subscribe(() => { - this.editForm.submitCompleted({ noReset: true }); + this.fieldForm.submitCompleted({ noReset: true }); }, error => { - this.editForm.submitFailed(error); + this.fieldForm.submitFailed(error); }); } } diff --git a/frontend/app/features/schemas/pages/schema/export/schema-export-form.component.html b/frontend/app/features/schemas/pages/schema/export/schema-export-form.component.html index 0284bd357..5e0434a08 100644 --- a/frontend/app/features/schemas/pages/schema/export/schema-export-form.component.html +++ b/frontend/app/features/schemas/pages/schema/export/schema-export-form.component.html @@ -20,7 +20,7 @@
- +
diff --git a/frontend/app/features/schemas/pages/schema/fields/field-wizard.component.html b/frontend/app/features/schemas/pages/schema/fields/field-wizard.component.html index 9169f1582..03a70bec4 100644 --- a/frontend/app/features/schemas/pages/schema/fields/field-wizard.component.html +++ b/frontend/app/features/schemas/pages/schema/fields/field-wizard.component.html @@ -37,7 +37,7 @@
- + @@ -66,10 +66,9 @@ + [field]="field" + [fieldForm]="editForm.form" + [patterns]="patternsState.patterns | async"> diff --git a/frontend/app/features/schemas/pages/schema/fields/field.component.html b/frontend/app/features/schemas/pages/schema/fields/field.component.html index 1854edbc9..6a3c02d9b 100644 --- a/frontend/app/features/schemas/pages/schema/fields/field.component.html +++ b/frontend/app/features/schemas/pages/schema/fields/field.component.html @@ -80,14 +80,12 @@
- + [patterns]="patterns" + [fieldForm]="editForm.form" + [field]="field" + [isEditable]="isEditable">
diff --git a/frontend/app/features/schemas/pages/schema/fields/field.component.scss b/frontend/app/features/schemas/pages/schema/fields/field.component.scss index f97ee0cc8..d3ab1fbe5 100644 --- a/frontend/app/features/schemas/pages/schema/fields/field.component.scss +++ b/frontend/app/features/schemas/pages/schema/fields/field.component.scss @@ -37,8 +37,7 @@ $padding: 1rem; .nested-fields { background: $color-theme-secondary; - border: 1px solid $color-border; - border-top-width: 0; + border: 0; padding: $padding; padding-left: 2 * $padding; position: relative; diff --git a/frontend/app/features/schemas/pages/schema/fields/field.component.ts b/frontend/app/features/schemas/pages/schema/fields/field.component.ts index 964cddc6c..0a804b260 100644 --- a/frontend/app/features/schemas/pages/schema/fields/field.component.ts +++ b/frontend/app/features/schemas/pages/schema/fields/field.component.ts @@ -33,7 +33,7 @@ export class FieldComponent implements OnChanges { public dropdown = new ModalModel(); - public trackByFieldFn: (index: number, field: NestedFieldDto) => any; + public trackByFieldFn: (_index: number, field: NestedFieldDto) => any; public isEditing = false; public isEditable = false; @@ -112,7 +112,7 @@ export class FieldComponent implements OnChanges { } } - public trackByField(index: number, field: NestedFieldDto) { + public trackByField(_index: number, field: NestedFieldDto) { return field.fieldId + this.schema.id; } } \ No newline at end of file diff --git a/frontend/app/features/schemas/pages/schema/fields/forms/field-form-common.component.html b/frontend/app/features/schemas/pages/schema/fields/forms/field-form-common.component.html index b6114b6d9..02c16ad8f 100644 --- a/frontend/app/features/schemas/pages/schema/fields/forms/field-form-common.component.html +++ b/frontend/app/features/schemas/pages/schema/fields/forms/field-form-common.component.html @@ -1,4 +1,4 @@ -
+
@@ -15,7 +15,7 @@
- + @@ -29,7 +29,7 @@
- + diff --git a/frontend/app/features/schemas/pages/schema/fields/forms/field-form-common.component.ts b/frontend/app/features/schemas/pages/schema/fields/forms/field-form-common.component.ts index bb7b649ad..a225924fb 100644 --- a/frontend/app/features/schemas/pages/schema/fields/forms/field-form-common.component.ts +++ b/frontend/app/features/schemas/pages/schema/fields/forms/field-form-common.component.ts @@ -18,10 +18,7 @@ export class FieldFormCommonComponent { public readonly standalone = { standalone: true }; @Input() - public editForm: FormGroup; - - @Input() - public editFormSubmitted = false; + public fieldForm: FormGroup; @Input() public field: FieldDto; diff --git a/frontend/app/features/schemas/pages/schema/fields/forms/field-form-ui.component.html b/frontend/app/features/schemas/pages/schema/fields/forms/field-form-ui.component.html index bef156f55..6ee23ff9d 100644 --- a/frontend/app/features/schemas/pages/schema/fields/forms/field-form-ui.component.html +++ b/frontend/app/features/schemas/pages/schema/fields/forms/field-form-ui.component.html @@ -1,29 +1,29 @@ - + - + - + - + - + - + - + - + - + \ No newline at end of file diff --git a/frontend/app/features/schemas/pages/schema/fields/forms/field-form-ui.component.ts b/frontend/app/features/schemas/pages/schema/fields/forms/field-form-ui.component.ts index 45044b6ea..bfecc0da5 100644 --- a/frontend/app/features/schemas/pages/schema/fields/forms/field-form-ui.component.ts +++ b/frontend/app/features/schemas/pages/schema/fields/forms/field-form-ui.component.ts @@ -16,7 +16,7 @@ import { FieldDto } from '@app/shared'; }) export class FieldFormUIComponent { @Input() - public editForm: FormGroup; + public fieldForm: FormGroup; @Input() public field: FieldDto; diff --git a/frontend/app/features/schemas/pages/schema/fields/forms/field-form-validation.component.html b/frontend/app/features/schemas/pages/schema/fields/forms/field-form-validation.component.html index 4a7b0f9dc..9341d0650 100644 --- a/frontend/app/features/schemas/pages/schema/fields/forms/field-form-validation.component.html +++ b/frontend/app/features/schemas/pages/schema/fields/forms/field-form-validation.component.html @@ -1,32 +1,32 @@ - + - + - + - + - + - + - + - + - + - + \ No newline at end of file diff --git a/frontend/app/features/schemas/pages/schema/fields/forms/field-form-validation.component.ts b/frontend/app/features/schemas/pages/schema/fields/forms/field-form-validation.component.ts index 073be28ed..60f722ac8 100644 --- a/frontend/app/features/schemas/pages/schema/fields/forms/field-form-validation.component.ts +++ b/frontend/app/features/schemas/pages/schema/fields/forms/field-form-validation.component.ts @@ -16,7 +16,7 @@ import { FieldDto, PatternDto } from '@app/shared'; }) export class FieldFormValidationComponent { @Input() - public editForm: FormGroup; + public fieldForm: FormGroup; @Input() public field: FieldDto; diff --git a/frontend/app/features/schemas/pages/schema/fields/forms/field-form.component.html b/frontend/app/features/schemas/pages/schema/fields/forms/field-form.component.html index 274beedc0..44070098a 100644 --- a/frontend/app/features/schemas/pages/schema/fields/forms/field-form.component.html +++ b/frontend/app/features/schemas/pages/schema/fields/forms/field-form.component.html @@ -13,18 +13,19 @@
+
- +
- +
- +
\ No newline at end of file diff --git a/frontend/app/features/schemas/pages/schema/fields/forms/field-form.component.ts b/frontend/app/features/schemas/pages/schema/fields/forms/field-form.component.ts index f9e109f01..16ab3e60a 100644 --- a/frontend/app/features/schemas/pages/schema/fields/forms/field-form.component.ts +++ b/frontend/app/features/schemas/pages/schema/fields/forms/field-form.component.ts @@ -22,17 +22,14 @@ export class FieldFormComponent implements AfterViewInit { public isEditable: boolean; @Input() - public editForm: FormGroup; + public fieldForm: FormGroup; @Input() - public editFormSubmitted: boolean; + public field: FieldDto; @Input() public patterns: ReadonlyArray; - @Input() - public field: FieldDto; - @Output() public cancel = new EventEmitter(); @@ -40,9 +37,9 @@ export class FieldFormComponent implements AfterViewInit { public ngAfterViewInit() { if (!this.isEditable) { - this.editForm.disable(); + this.fieldForm.disable(); } else { - this.editForm.enable(); + this.fieldForm.enable(); } } diff --git a/frontend/app/features/schemas/pages/schema/fields/schema-fields.component.ts b/frontend/app/features/schemas/pages/schema/fields/schema-fields.component.ts index 3866ada01..7cd45c635 100644 --- a/frontend/app/features/schemas/pages/schema/fields/schema-fields.component.ts +++ b/frontend/app/features/schemas/pages/schema/fields/schema-fields.component.ts @@ -22,7 +22,7 @@ export class SchemaFieldsComponent implements OnInit { public addFieldDialog = new DialogModel(); - public trackByFieldFn: (index: number, field: FieldDto) => any; + public trackByFieldFn: (_index: number, field: FieldDto) => any; constructor( public readonly schemasState: SchemasState, @@ -39,7 +39,7 @@ export class SchemaFieldsComponent implements OnInit { this.schemasState.orderFields(this.schema, sorted(event)).subscribe(); } - public trackByField(index: number, field: FieldDto) { + public trackByField(_index: number, field: FieldDto) { return field.fieldId + this.schema.id; } } \ No newline at end of file diff --git a/frontend/app/features/schemas/pages/schema/fields/types/array-validation.component.html b/frontend/app/features/schemas/pages/schema/fields/types/array-validation.component.html index 852ce60e8..26fd16182 100644 --- a/frontend/app/features/schemas/pages/schema/fields/types/array-validation.component.html +++ b/frontend/app/features/schemas/pages/schema/fields/types/array-validation.component.html @@ -1,4 +1,4 @@ -
+
diff --git a/frontend/app/features/schemas/pages/schema/fields/types/array-validation.component.ts b/frontend/app/features/schemas/pages/schema/fields/types/array-validation.component.ts index 714e04a67..a99ddaa44 100644 --- a/frontend/app/features/schemas/pages/schema/fields/types/array-validation.component.ts +++ b/frontend/app/features/schemas/pages/schema/fields/types/array-validation.component.ts @@ -16,7 +16,7 @@ import { ArrayFieldPropertiesDto, FieldDto, SchemaTagSource } from '@app/shared' }) export class ArrayValidationComponent implements OnInit { @Input() - public editForm: FormGroup; + public fieldForm: FormGroup; @Input() public field: FieldDto; @@ -30,10 +30,10 @@ export class ArrayValidationComponent implements OnInit { } public ngOnInit() { - this.editForm.setControl('maxItems', + this.fieldForm.setControl('maxItems', new FormControl(this.properties.maxItems)); - this.editForm.setControl('minItems', + this.fieldForm.setControl('minItems', new FormControl(this.properties.minItems)); } } \ No newline at end of file diff --git a/frontend/app/features/schemas/pages/schema/fields/types/assets-ui.component.html b/frontend/app/features/schemas/pages/schema/fields/types/assets-ui.component.html index 905a62088..05ae4ff8c 100644 --- a/frontend/app/features/schemas/pages/schema/fields/types/assets-ui.component.html +++ b/frontend/app/features/schemas/pages/schema/fields/types/assets-ui.component.html @@ -1,5 +1,5 @@ -
+
diff --git a/frontend/app/features/schemas/pages/schema/fields/types/assets-ui.component.ts b/frontend/app/features/schemas/pages/schema/fields/types/assets-ui.component.ts index 61ddbe296..cb763b5a0 100644 --- a/frontend/app/features/schemas/pages/schema/fields/types/assets-ui.component.ts +++ b/frontend/app/features/schemas/pages/schema/fields/types/assets-ui.component.ts @@ -16,7 +16,7 @@ import { AssetsFieldPropertiesDto, FieldDto } from '@app/shared'; }) export class AssetsUIComponent implements OnInit { @Input() - public editForm: FormGroup; + public fieldForm: FormGroup; @Input() public field: FieldDto; @@ -25,10 +25,10 @@ export class AssetsUIComponent implements OnInit { public properties: AssetsFieldPropertiesDto; public ngOnInit() { - this.editForm.setControl('previewMode', + this.fieldForm.setControl('previewMode', new FormControl(this.properties.previewMode)); - this.editForm.setControl('resolveFirst', + this.fieldForm.setControl('resolveFirst', new FormControl(this.properties.resolveFirst)); } } \ No newline at end of file diff --git a/frontend/app/features/schemas/pages/schema/fields/types/assets-validation.component.html b/frontend/app/features/schemas/pages/schema/fields/types/assets-validation.component.html index 7f620efbc..fffc9d8b4 100644 --- a/frontend/app/features/schemas/pages/schema/fields/types/assets-validation.component.html +++ b/frontend/app/features/schemas/pages/schema/fields/types/assets-validation.component.html @@ -1,4 +1,4 @@ -
+
diff --git a/frontend/app/features/schemas/pages/schema/fields/types/assets-validation.component.ts b/frontend/app/features/schemas/pages/schema/fields/types/assets-validation.component.ts index d5889a051..d771f8f70 100644 --- a/frontend/app/features/schemas/pages/schema/fields/types/assets-validation.component.ts +++ b/frontend/app/features/schemas/pages/schema/fields/types/assets-validation.component.ts @@ -16,7 +16,7 @@ import { AssetsFieldPropertiesDto, FieldDto } from '@app/shared'; }) export class AssetsValidationComponent implements OnInit { @Input() - public editForm: FormGroup; + public fieldForm: FormGroup; @Input() public field: FieldDto; @@ -25,43 +25,43 @@ export class AssetsValidationComponent implements OnInit { public properties: AssetsFieldPropertiesDto; public ngOnInit() { - this.editForm.setControl('minItems', + this.fieldForm.setControl('minItems', new FormControl(this.properties.minItems)); - this.editForm.setControl('maxItems', + this.fieldForm.setControl('maxItems', new FormControl(this.properties.maxItems)); - this.editForm.setControl('minSize', + this.fieldForm.setControl('minSize', new FormControl(this.properties.minSize)); - this.editForm.setControl('maxSize', + this.fieldForm.setControl('maxSize', new FormControl(this.properties.maxSize)); - this.editForm.setControl('allowedExtensions', + this.fieldForm.setControl('allowedExtensions', new FormControl(this.properties.allowedExtensions)); - this.editForm.setControl('mustBeImage', + this.fieldForm.setControl('mustBeImage', new FormControl(this.properties.mustBeImage)); - this.editForm.setControl('minWidth', + this.fieldForm.setControl('minWidth', new FormControl(this.properties.minWidth)); - this.editForm.setControl('maxWidth', + this.fieldForm.setControl('maxWidth', new FormControl(this.properties.maxWidth)); - this.editForm.setControl('minHeight', + this.fieldForm.setControl('minHeight', new FormControl(this.properties.minHeight)); - this.editForm.setControl('maxHeight', + this.fieldForm.setControl('maxHeight', new FormControl(this.properties.maxHeight)); - this.editForm.setControl('aspectWidth', + this.fieldForm.setControl('aspectWidth', new FormControl(this.properties.aspectWidth)); - this.editForm.setControl('aspectHeight', + this.fieldForm.setControl('aspectHeight', new FormControl(this.properties.aspectHeight)); - this.editForm.setControl('allowDuplicates', + this.fieldForm.setControl('allowDuplicates', new FormControl(this.properties.allowDuplicates)); } } \ No newline at end of file diff --git a/frontend/app/features/schemas/pages/schema/fields/types/boolean-ui.component.html b/frontend/app/features/schemas/pages/schema/fields/types/boolean-ui.component.html index 95534937d..d9a5ddd1b 100644 --- a/frontend/app/features/schemas/pages/schema/fields/types/boolean-ui.component.html +++ b/frontend/app/features/schemas/pages/schema/fields/types/boolean-ui.component.html @@ -1,4 +1,4 @@ -
+
@@ -25,14 +25,14 @@
-