Browse Source

Form model (#547)

* Conditional field rules: Require, Hide, Disable
pull/548/head
Sebastian Stehle 5 years ago
committed by GitHub
parent
commit
2298a4d9d8
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      Dockerfile
  2. 2
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldNames.cs
  3. 43
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldRule.cs
  4. 16
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldRuleAction.cs
  5. 30
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldRules.cs
  6. 8
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/Json/JsonSchemaModel.cs
  7. 28
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/Schema.cs
  8. 5
      backend/src/Squidex.Domain.Apps.Core.Operations/EventSynchronization/SchemaSynchronizer.cs
  9. 4
      backend/src/Squidex.Domain.Apps.Entities/Contents/Text/Lucene/Storage/AssetIndexStorage.cs
  10. 30
      backend/src/Squidex.Domain.Apps.Entities/Schemas/Commands/ConfigureFieldRules.cs
  11. 25
      backend/src/Squidex.Domain.Apps.Entities/Schemas/Commands/FieldRuleCommand.cs
  12. 9
      backend/src/Squidex.Domain.Apps.Entities/Schemas/Commands/UpsertCommand.cs
  13. 37
      backend/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchema.cs
  14. 15
      backend/src/Squidex.Domain.Apps.Entities/Schemas/SchemaDomainObject.cs
  15. 7
      backend/src/Squidex.Domain.Apps.Entities/Schemas/State/SchemaState.cs
  16. 1
      backend/src/Squidex.Domain.Apps.Entities/Squidex.Domain.Apps.Entities.csproj
  17. 18
      backend/src/Squidex.Domain.Apps.Events/Schemas/SchemaFieldRulesConfigured.cs
  18. 1
      backend/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj
  19. 19
      backend/src/Squidex.Infrastructure/Validation/CustomValidators.cs
  20. 29
      backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/ConfigureFieldRulesDto.cs
  21. 44
      backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/FieldRuleDto.cs
  22. 8
      backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDetailsDto.cs
  23. 1
      backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDto.cs
  24. 28
      backend/src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs
  25. 12
      backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/SchemaTests.cs
  26. 18
      backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/EventSynchronization/SchemaSynchronizerTests.cs
  27. 45
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/GuardSchemaTests.cs
  28. 27
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/SchemaDomainObjectTests.cs
  29. 2
      frontend/app-config/karma-test-shim.js
  30. 2
      frontend/app/features/administration/pages/event-consumers/event-consumers-page.component.ts
  31. 10
      frontend/app/features/administration/pages/users/user-page.component.html
  32. 2
      frontend/app/features/administration/pages/users/users-page.component.ts
  33. 2
      frontend/app/features/apps/pages/apps-page.component.ts
  34. 2
      frontend/app/features/apps/pages/news-dialog.component.ts
  35. 2
      frontend/app/features/assets/pages/asset-tags.component.ts
  36. 2
      frontend/app/features/assets/pages/assets-filters-page.component.ts
  37. 1
      frontend/app/features/content/declarations.ts
  38. 3
      frontend/app/features/content/module.ts
  39. 30
      frontend/app/features/content/pages/content/content-field.component.html
  40. 55
      frontend/app/features/content/pages/content/content-field.component.ts
  41. 2
      frontend/app/features/content/pages/content/content-history-page.component.ts
  42. 6
      frontend/app/features/content/pages/content/content-page.component.html
  43. 11
      frontend/app/features/content/pages/content/content-page.component.ts
  44. 18
      frontend/app/features/content/pages/content/content-section.component.html
  45. 21
      frontend/app/features/content/pages/content/content-section.component.ts
  46. 2
      frontend/app/features/content/pages/contents/contents-page.component.ts
  47. 2
      frontend/app/features/content/pages/schemas/schemas-page.component.ts
  48. 23
      frontend/app/features/content/shared/forms/array-editor.component.html
  49. 47
      frontend/app/features/content/shared/forms/array-editor.component.ts
  50. 7
      frontend/app/features/content/shared/forms/array-item.component.html
  51. 27
      frontend/app/features/content/shared/forms/array-item.component.ts
  52. 15
      frontend/app/features/content/shared/forms/array-section.component.html
  53. 21
      frontend/app/features/content/shared/forms/array-section.component.ts
  54. 2
      frontend/app/features/content/shared/forms/assets-editor.component.ts
  55. 8
      frontend/app/features/content/shared/forms/field-editor.component.html
  56. 17
      frontend/app/features/content/shared/forms/field-editor.component.ts
  57. 2
      frontend/app/features/content/shared/forms/stock-photo-editor.component.ts
  58. 45
      frontend/app/features/content/shared/group-fields.pipe.ts
  59. 6
      frontend/app/features/content/shared/references/content-creator.component.html
  60. 2
      frontend/app/features/content/shared/references/content-creator.component.ts
  61. 2
      frontend/app/features/content/shared/references/content-selector.component.ts
  62. 2
      frontend/app/features/content/shared/references/references-editor.component.ts
  63. 2
      frontend/app/features/rules/pages/events/rule-events-page.component.ts
  64. 2
      frontend/app/features/rules/pages/rules/actions/generic-action.component.html
  65. 3
      frontend/app/features/rules/pages/rules/actions/generic-action.component.ts
  66. 18
      frontend/app/features/rules/pages/rules/rule-wizard.component.html
  67. 2
      frontend/app/features/rules/pages/rules/rules-page.component.ts
  68. 2
      frontend/app/features/rules/pages/rules/triggers/asset-changed-trigger.component.html
  69. 3
      frontend/app/features/rules/pages/rules/triggers/asset-changed-trigger.component.ts
  70. 2
      frontend/app/features/rules/pages/rules/triggers/comment-trigger.component.html
  71. 3
      frontend/app/features/rules/pages/rules/triggers/comment-trigger.component.ts
  72. 5
      frontend/app/features/rules/pages/rules/triggers/content-changed-trigger.component.ts
  73. 2
      frontend/app/features/rules/pages/rules/triggers/schema-changed-trigger.component.html
  74. 3
      frontend/app/features/rules/pages/rules/triggers/schema-changed-trigger.component.ts
  75. 4
      frontend/app/features/rules/pages/rules/triggers/usage-trigger.component.html
  76. 3
      frontend/app/features/rules/pages/rules/triggers/usage-trigger.component.ts
  77. 1
      frontend/app/features/schemas/declarations.ts
  78. 3
      frontend/app/features/schemas/module.ts
  79. 8
      frontend/app/features/schemas/pages/schema/common/schema-edit-form.component.html
  80. 12
      frontend/app/features/schemas/pages/schema/common/schema-edit-form.component.ts
  81. 2
      frontend/app/features/schemas/pages/schema/export/schema-export-form.component.html
  82. 9
      frontend/app/features/schemas/pages/schema/fields/field-wizard.component.html
  83. 10
      frontend/app/features/schemas/pages/schema/fields/field.component.html
  84. 3
      frontend/app/features/schemas/pages/schema/fields/field.component.scss
  85. 4
      frontend/app/features/schemas/pages/schema/fields/field.component.ts
  86. 6
      frontend/app/features/schemas/pages/schema/fields/forms/field-form-common.component.html
  87. 5
      frontend/app/features/schemas/pages/schema/fields/forms/field-form-common.component.ts
  88. 18
      frontend/app/features/schemas/pages/schema/fields/forms/field-form-ui.component.html
  89. 2
      frontend/app/features/schemas/pages/schema/fields/forms/field-form-ui.component.ts
  90. 20
      frontend/app/features/schemas/pages/schema/fields/forms/field-form-validation.component.html
  91. 2
      frontend/app/features/schemas/pages/schema/fields/forms/field-form-validation.component.ts
  92. 7
      frontend/app/features/schemas/pages/schema/fields/forms/field-form.component.html
  93. 11
      frontend/app/features/schemas/pages/schema/fields/forms/field-form.component.ts
  94. 4
      frontend/app/features/schemas/pages/schema/fields/schema-fields.component.ts
  95. 2
      frontend/app/features/schemas/pages/schema/fields/types/array-validation.component.html
  96. 6
      frontend/app/features/schemas/pages/schema/fields/types/array-validation.component.ts
  97. 2
      frontend/app/features/schemas/pages/schema/fields/types/assets-ui.component.html
  98. 6
      frontend/app/features/schemas/pages/schema/fields/types/assets-ui.component.ts
  99. 2
      frontend/app/features/schemas/pages/schema/fields/types/assets-validation.component.html
  100. 28
      frontend/app/features/schemas/pages/schema/fields/types/assets-validation.component.ts

2
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

2
backend/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldNames.cs

@ -15,7 +15,7 @@ namespace Squidex.Domain.Apps.Core.Schemas
{
private static readonly List<string> EmptyNames = new List<string>();
public static readonly FieldNames Empty = new FieldNames(new List<string>());
public static readonly FieldNames Empty = new FieldNames(EmptyNames);
public FieldNames(params string[] fields)
: base(fields?.ToList() ?? EmptyNames)

43
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);
}
}
}

16
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
}
}

30
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<FieldRule>
{
private static readonly List<FieldRule> EmptyRules = new List<FieldRule>();
public static readonly FieldRules Empty = new FieldRules(EmptyRules);
public FieldRules(params FieldRule[] fields)
: base(fields?.ToList() ?? EmptyRules)
{
}
public FieldRules(IList<FieldRule> list)
: base(list ?? EmptyRules)
{
}
}
}

8
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);

28
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<RootField> fields = FieldCollection<RootField>.Empty;
private IReadOnlyDictionary<string, string> 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()
{

5
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 });
}
}
}

4
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<LuceneDirectory> CreateDirectoryAsync(Guid schemaId)
public async Task<LuceneDirectory> 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)
{

30
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<FieldRuleCommand>? 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;
}
}
}
}

25
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);
}
}
}

9
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<Squidex.Domain.Apps.Entities.Schemas.Commands.FieldRuleCommand>;
using SchemaFields = System.Collections.Generic.List<Squidex.Domain.Apps.Entities.Schemas.Commands.UpsertSchemaField>;
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);

37
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<FieldRuleCommand>? 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<string, bool> isAllowed)
{
if (fields != null)

15
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());

7
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();

1
backend/src/Squidex.Domain.Apps.Entities/Squidex.Domain.Apps.Entities.csproj

@ -20,6 +20,7 @@
<PackageReference Include="CsvHelper" Version="15.0.5" />
<PackageReference Include="Elasticsearch.Net" Version="7.7.1" />
<PackageReference Include="Equals.Fody" Version="4.0.1" PrivateAssets="all" />
<PackageReference Include="FluentValidation" Version="9.0.1" />
<PackageReference Include="Fody" Version="6.2.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

18
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; }
}
}

1
backend/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj

@ -11,6 +11,7 @@
<ItemGroup>
<PackageReference Include="Equals.Fody" Version="4.0.1" PrivateAssets="all" />
<PackageReference Include="FluentFTP" Version="32.4.3" />
<PackageReference Include="FluentValidation" Version="9.0.1" />
<PackageReference Include="Fody" Version="6.2.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

19
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<T, string> Slug<T>(this IRuleBuilder<T, string> ruleBuilder)
{
return ruleBuilder.Must(x => x.IsSlug()).WithMessage("{PropertyName} must be a valid slug.");
}
}
}

29
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
{
/// <summary>
/// The field rules to configure.
/// </summary>
public List<FieldRuleDto>? FieldRules { get; set; }
public ConfigureFieldRules ToCommand()
{
return new ConfigureFieldRules
{
FieldRules = FieldRules?.Select(x => x.ToCommand()).ToList()
};
}
}
}

44
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
{
/// <summary>
/// The action to perform when the condition is met.
/// </summary>
[Required]
public FieldRuleAction Action { get; set; }
/// <summary>
/// The field to update.
/// </summary>
[Required]
public string Field { get; set; }
/// <summary>
/// The condition.
/// </summary>
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());
}
}
}

8
backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDetailsDto.cs

@ -42,6 +42,11 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models
[Required]
public List<string> FieldsInReferences { get; set; }
/// <summary>
/// The field rules.
/// </summary>
public List<FieldRuleDto> FieldRules { get; set; }
/// <summary>
/// The list of fields.
/// </summary>
@ -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<string, string>(schema.SchemaDef.PreviewUrls);

1
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<SchemasController>(x => nameof(x.PutSchema), values));
AddPutLink("update/sync", resources.Url<SchemasController>(x => nameof(x.PutSchemaSync), values));
AddPutLink("update/urls", resources.Url<SchemasController>(x => nameof(x.PutPreviewUrls), values));
AddPutLink("update/rules", resources.Url<SchemasController>(x => nameof(x.PutRules), values));
AddPutLink("update/category", resources.Url<SchemasController>(x => nameof(x.PutCategory), values));
}

28
backend/src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs

@ -208,6 +208,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas
/// <param name="request">The preview urls for the schema.</param>
/// <returns>
/// 200 => Schema updated.
/// 400 => Schema urls are not valid.
/// 404 => Schema or app not found.
/// </returns>
[HttpPut]
@ -232,7 +233,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas
/// <param name="request">The schema scripts object that needs to updated.</param>
/// <returns>
/// 200 => Schema updated.
/// 400 => Schema properties are not valid.
/// 400 => Schema scripts are not valid.
/// 404 => Schema or app not found.
/// </returns>
[HttpPut]
@ -249,6 +250,31 @@ namespace Squidex.Areas.Api.Controllers.Schemas
return Ok(response);
}
/// <summary>
/// Update the rules.
/// </summary>
/// <param name="app">The name of the app.</param>
/// <param name="name">The name of the schema.</param>
/// <param name="request">The schema rules object that needs to updated.</param>
/// <returns>
/// 200 => Schema updated.
/// 400 => Schema rules are not valid.
/// 404 => Schema or app not found.
/// </returns>
[HttpPut]
[Route("apps/{app}/schemas/{name}/rules/")]
[ProducesResponseType(typeof(SchemaDetailsDto), 200)]
[ApiPermissionOrAnonymous(Permissions.AppSchemasUpdate)]
[ApiCosts(1)]
public async Task<IActionResult> PutRules(string app, string name, [FromBody] ConfigureFieldRulesDto request)
{
var command = request.ToCommand();
var response = await InvokeCommandAsync(app, command);
return Ok(response);
}
/// <summary>
/// Publish a schema.
/// </summary>

12
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<string, string>

18
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()
{

45
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<FieldRuleCommand>
{
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<FieldRuleCommand>
{
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()
{

27
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("<query-script>", 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<FieldRuleCommand>
{
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()
{

2
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);

2
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;
}

10
frontend/app/features/administration/pages/users/user-page.component.html

@ -43,14 +43,14 @@
<div class="form-group">
<label for="email">Email <small class="hint">(required)</small></label>
<sqx-control-errors for="email" [submitted]="userForm.submitted | async"></sqx-control-errors>
<sqx-control-errors for="email" ></sqx-control-errors>
<input type="email" class="form-control" id="email" maxlength="100" formControlName="email" autocomplete="off" />
</div>
<div class="form-group">
<label for="displayName">Display Name <small class="hint">(required)</small></label>
<sqx-control-errors for="displayName" [submitted]="userForm.submitted | async"></sqx-control-errors>
<sqx-control-errors for="displayName"></sqx-control-errors>
<input type="text" class="form-control" id="displayName" maxlength="100" formControlName="displayName" autocomplete="off" spellcheck="false" />
</div>
@ -59,7 +59,7 @@
<div class="form-group">
<label for="password">Password</label>
<sqx-control-errors for="password" [submitted]="userForm.submitted | async"></sqx-control-errors>
<sqx-control-errors for="password"></sqx-control-errors>
<input type="password" class="form-control" id="password" maxlength="100" formControlName="password" autocomplete="off" />
</div>
@ -67,7 +67,7 @@
<div class="form-group">
<label for="password">Confirm Password</label>
<sqx-control-errors for="passwordConfirm" [submitted]="userForm.submitted | async"></sqx-control-errors>
<sqx-control-errors for="passwordConfirm"></sqx-control-errors>
<input type="password" class="form-control" id="passwordConfirm" maxlength="100" formControlName="passwordConfirm" autocomplete="off" />
</div>
@ -76,7 +76,7 @@
<div class="form-group form-group-section">
<label for="permissions">Permissions</label>
<sqx-control-errors for="permissions" [submitted]="userForm.submitted | async"></sqx-control-errors>
<sqx-control-errors for="permissions"></sqx-control-errors>
<textarea class="form-control" id="permissions" formControlName="permissions" placeholder="Separate by line" autocomplete="off" spellcheck="false"></textarea>
</div>

2
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;
}
}

2
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;
}
}

2
frontend/app/features/apps/pages/news-dialog.component.ts

@ -20,7 +20,7 @@ export class NewsDialogComponent {
@Input()
public features: ReadonlyArray<FeatureDto>;
public trackByFeature(index: number, feature: FeatureDto) {
public trackByFeature(_index: number, feature: FeatureDto) {
return feature;
}
}

2
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;
}
}

2
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;
}
}

1
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';

3
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,

30
frontend/app/features/content/pages/content/content-field.component.html

@ -1,13 +1,13 @@
<div class="row no-gutters" [class.compare]="fieldFormCompare">
<div [class.col-12]="!fieldFormCompare" [class.col-6]="fieldFormCompare">
<div class="table-items-row" [class.field-invalid]="isInvalid | async">
<div class="row no-gutters" [class.compare]="formModelCompare">
<div [class.col-12]="!formModelCompare" [class.col-6]="formModelCompare">
<div class="table-items-row" [class.field-invalid]="isInvalid | async" *ngIf="!(formModel.hiddenChanges | async)">
<div class="languages-buttons">
<button *ngIf="canTranslate" type="button" class="btn btn-text-secondary btn-sm mr-1" (click)="translate()" title="Autotranslate from master language">
<i class="icon-translate"></i>
</button>
<sqx-field-languages
[field]="field"
[field]="formModel.field"
(languageChange)="languageChange.emit($event)"
[language]="language"
[languages]="languages"
@ -17,12 +17,11 @@
</div>
<ng-container *ngIf="showAllControls; else singleControl">
<div class="form-group" *ngFor="let language of languages; trackBy: trackByLanguage">
<div class="form-group" *ngFor="let language of formModel; trackBy: trackByLanguage">
<sqx-field-editor
[control]="fieldForm.controls[language.iso2Code]"
[field]="field"
[form]="form"
[formContext]="formContext"
[formModel="formModel.getField(language.iso2Code)"
[language]="language"
[languages]="languages"
[displaySuffix]="prefix(language)">
@ -32,10 +31,9 @@
<ng-template #singleControl>
<sqx-field-editor
[control]="getControl()"
[field]="field"
[form]="form"
[formContext]="formContext"
[formModel]="getControl()"
[language]="language"
[languages]="languages">
</sqx-field-editor>
@ -43,15 +41,15 @@
</div>
</div>
<div class="col-6 col-right" *ngIf="fieldFormCompare">
<div class="col-6 col-right" *ngIf="formModelCompare">
<button type="button" class="btn btn-primary btn-sm field-copy" (click)="copy()" *ngIf="isDifferent | async">
<i class="icon-arrow_back"></i>
</button>
<div class="table-items-row">
<div class="table-items-row" *ngIf="!(formModelCompare!.hiddenChanges | async)">
<div class="languages-buttons">
<sqx-field-languages
[field]="field"
[field]="formModelCompare!.field"
(languageChange)="languageChange.emit($event)"
[language]="language"
[languages]="languages"
@ -63,8 +61,9 @@
<ng-container *ngIf="showAllControls; else singleControlCompare">
<div class="form-group" *ngFor="let language of languages; trackBy: trackByLanguage">
<sqx-field-editor
[control]="fieldFormCompare?.controls[language.iso2Code]"
[field]="field"
[form]="formCompare"
[formContext]="formContext"
[formModel="formModelCompare.getField(language.iso2Code)"
[language]="language"
[languages]="languages"
[displaySuffix]="prefix(language)">
@ -74,8 +73,7 @@
<ng-template #singleControlCompare>
<sqx-field-editor
[control]="getControlCompare()"
[field]="field"
[formModel]="getControlCompare()"
[language]="language"
[languages]="languages">
</sqx-field-editor>

55
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`;
}
}

2
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;
}
}

6
frontend/app/features/content/pages/content/content-page.component.html

@ -81,14 +81,14 @@
</ng-container>
<div content>
<sqx-content-section *ngFor="let section of schema.fields | sqxGroupFields; trackBy: trackBySection"
<sqx-content-section *ngFor="let section of contentForm.sections; trackBy: trackBySection"
[(language)]="language"
[form]="contentForm"
[formCompare]="contentFormCompare"
[formContext]="formContext"
[formSection]="section"
[languages]="languages"
[schema]="schema"
[section]="section">
[schema]="schema">
</sqx-content-section>
</div>
</sqx-list-view>

11
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<RootFieldDto>) {
public trackBySection(_index: number, section: FieldSection<RootFieldDto, FieldForm>) {
return section.separator?.fieldId;
}
}

18
frontend/app/features/content/pages/content/content-section.component.html

@ -1,4 +1,5 @@
<div class="header" *ngIf="section.separator; let separator">
<ng-container *ngIf="!(formSection.hiddenChanges | async) || formCompare">
<div class="header" *ngIf="formSection.separator; let separator">
<div class="row no-gutters">
<div class="col-auto">
<button type="button" class="btn btn-sm btn-text-secondary" (click)="toggle()">
@ -13,16 +14,17 @@
</sqx-form-hint>
</div>
</div>
</div>
</div>
</ng-container>
<div [class.hidden]="isCollapsed">
<sqx-content-field *ngFor="let field of section.fields; trackBy: trackByField"
[field]="field"
[fieldForm]="getFieldForm(field)"
[fieldFormCompare]="getFieldFormCompare(field)"
<div [class.hidden]="isCollapsed && !formCompare">
<sqx-content-field *ngFor="let field of formSection.fields; trackBy: trackByField"
(languageChange)="languageChange.emit($event)"
[form]="form"
[formCompare]="formCompare"
[formContext]="formContext"
(languageChange)="languageChange.emit($event)"
[formModel]="field"
[formModelCompare]="getFieldFormCompare(field)"
[language]="language"
[languages]="languages"
[schema]="schema">

21
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<RootFieldDto, FieldForm>;
@Input()
public section: FieldSection<RootFieldDto>;
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`;
}
}

2
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;
}

2
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;
}
}

23
frontend/app/features/content/shared/forms/array-editor.component.html

@ -1,26 +1,23 @@
<div class="array-container" *ngIf="arrayControl.controls.length > 0"
<div class="array-container" *ngIf="formModel.items.length > 0"
cdkDropList
[cdkDropListDisabled]="false"
[cdkDropListData]="arrayControl.controls"
[cdkDropListData]="formModel.items"
(cdkDropListDropped)="sort($event)">
<div *ngFor="let itemForm of arrayControl.controls; let i = index;"
<div *ngFor="let itemForm of formModel.items; index as i; last as isLast; first as isFirst"
class="table-drag item"
cdkDrag
cdkDragLockAxis="y">
<sqx-array-item
[form]="form"
[formContext]="formContext"
[field]="field"
[isDisabled]="arrayControl.disabled"
[isFirst]="i === 0"
[isLast]="i === arrayControl.controls.length - 1"
[formModel]="itemForm"
[index]="i"
[itemForm]="itemForm"
[isDisabled]="formModel.form.disabled"
[isFirst]="isFirst"
[isLast]="isLast"
[language]="language"
[languages]="languages"
(clone)="itemAdd(itemForm)"
(move)="move(itemForm, $event)"
(remove)="itemRemove(i)">
(clone)="itemAdd(itemForm)" (move)="move(itemForm, $event)" (remove)="itemRemove(i)">
<i cdkDragHandle class="icon-drag2"></i>
</sqx-array-item>
</div>
@ -28,12 +25,12 @@
<div class="row">
<div class="col">
<button type="button" class="btn btn-success" [disabled]="field.nested.length === 0 || arrayControl.disabled" (click)="itemAdd(undefined)">
<button type="button" class="btn btn-success" [disabled]="field.nested.length === 0 || formModel.form.disabled" (click)="itemAdd(undefined)">
Add Item
</button>
</div>
<div class="col-auto" *ngIf="arrayControl.controls.length > 0">
<div class="col-auto" *ngIf="formModel.items.length > 0">
<button type="button" class="btn btn-text-secondary" (click)="expandAll()" title="Expand all items">
<i class="icon-plus-square"></i>
</button>

47
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<AppLanguageDto>;
@Input()
public arrayControl: FormArray;
@ViewChildren(ArrayItemComponent)
public children: QueryList<ArrayItemComponent>;
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?: FieldArrayItemForm) {
this.formModel.addItem(value);
}
public itemAdd(value?: FormGroup) {
this.form.arrayItemInsert(this.field, this.language, value);
public sort(event: CdkDragDrop<ReadonlyArray<FieldArrayItemForm>>) {
this.formModel.sort(sorted(event));
this.reset();
}
public sort(event: CdkDragDrop<ReadonlyArray<AbstractControl>>) {
this.sortInternal(sorted(event));
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<AbstractControl>) {
for (let i = 0; i < controls.length; i++) {
this.arrayControl.setControl(i, controls[i]);
}
this.reset();
}
}

7
frontend/app/features/content/shared/forms/array-item.component.html

@ -43,14 +43,13 @@
</div>
<div class="card-body" [class.hidden]="isCollapsed">
<div class="form-group" *ngFor="let section of field.nested | sqxGroupFields; trackBy: trackBySection">
<div class="form-group" *ngFor="let section of formModel.sections">
<sqx-array-section
[form]="form"
[formContext]="formContext"
[itemForm]="itemForm"
[formSection]="section"
[language]="language"
[languages]="languages"
[section]="section">
[languages]="languages">
</sqx-array-section>
</div>
</div>

27
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<NestedFieldDto>) {
public trackBySection(_index: number, section: FieldSection<NestedFieldDto, FieldArrayItemValueForm>) {
return section.separator?.fieldId;
}
}

15
frontend/app/features/content/shared/forms/array-section.component.html

@ -1,18 +1,19 @@
<div class="header" *ngIf="section.separator; let separator">
<ng-container *ngIf="!(formSection.hiddenChanges | async)">
<div class="header" *ngIf="formSection.separator; let separator">
<h3>{{separator!.displayName}}</h3>
<sqx-form-hint *ngIf="separator!.properties.hints?.length > 0">
{{separator!.properties.hints}}
</sqx-form-hint>
</div>
</div>
<div class="form-group" *ngFor="let field of section.fields; trackBy: trackByField">
<sqx-field-editor
[control]="getControl(field)"
[field]="field"
<div class="form-group" *ngFor="let child of formSection.fields; trackBy: trackByField">
<sqx-field-editor *ngIf="!(child.hiddenChanges | async)"
[form]="form"
[formContext]="formContext"
[formModel]="child"
[language]="language"
[languages]="languages">
</sqx-field-editor>
</div>
</div>
</ng-container>

21
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<NestedFieldDto, FieldArrayItemForm>;
@Input()
public languages: ReadonlyArray<AppLanguageDto>;
public language: AppLanguageDto;
@Input()
public section: FieldSection<NestedFieldDto>;
public languages: ReadonlyArray<AppLanguageDto>;
@ViewChildren(FieldEditorComponent)
public editors: QueryList<FieldEditorComponent>;
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;
}
}

2
frontend/app/features/content/shared/forms/assets-editor.component.ts

@ -172,7 +172,7 @@ export class AssetsEditorComponent extends StatefulControlComponent<State, Reado
this.callTouched();
}
public trackByAsset(index: number, asset: AssetDto) {
public trackByAsset(_index: number, asset: AssetDto) {
return asset.id;
}
}

8
frontend/app/features/content/shared/forms/field-editor.component.html

@ -1,11 +1,11 @@
<div *ngIf="field">
<div *ngIf="formModel">
<label>
{{field.displayName}} {{displaySuffix}} <span class="field-required" [class.hidden]="!field.properties.isRequired">*</span>
</label>
<small class="field-disabled pl-1" *ngIf="field.isDisabled">Disabled</small>
<sqx-control-errors *ngIf="form" [for]="editorControl" [fieldName]="field.displayName" [submitted]="form.submitted | async"></sqx-control-errors>
<sqx-control-errors *ngIf="form" [for]="editorControl" [fieldName]="field.displayName"></sqx-control-errors>
<div>
<ng-container *ngIf="field.properties.editorUrl; else noEditor">
@ -20,10 +20,8 @@
<ng-container [ngSwitch]="field.properties.fieldType">
<ng-container *ngSwitchCase="'Array'">
<sqx-array-editor
[arrayControl]="arrayControl"
[form]="form"
[formModel]="formModel"
[formContext]="formContext"
[field]="rootField"
[language]="language"
[languages]="languages">
</sqx-array-editor>

17
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<FieldDto, AbstractControl>;
@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();

2
frontend/app/features/content/shared/forms/stock-photo-editor.component.ts

@ -112,7 +112,7 @@ export class StockPhotoEditorComponent extends StatefulControlComponent<State, s
return photo.url === this.valueControl.value;
}
public trackByPhoto(index: number, photo: StockPhotoDto) {
public trackByPhoto(_index: number, photo: StockPhotoDto) {
return photo.thumbUrl;
}
}

45
frontend/app/features/content/shared/group-fields.pipe.ts

@ -1,45 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Pipe, PipeTransform } from '@angular/core';
import { FieldDto } from '@app/shared';
export interface FieldSection<T> {
separator?: T;
fields: ReadonlyArray<T>;
}
@Pipe({
name: 'sqxGroupFields',
pure: true
})
export class GroupFieldsPipe<T extends FieldDto> implements PipeTransform {
public transform(fields: ReadonlyArray<T>) {
const sections: FieldSection<T>[] = [];
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;
}
}

6
frontend/app/features/content/shared/references/content-creator.component.html

@ -40,13 +40,13 @@
<ng-container content>
<ng-container *ngIf="schema && contentForm">
<form [formGroup]="contentForm.form" (ngSubmit)="saveAndPublish()">
<sqx-content-section *ngFor="let section of schema.fields | sqxGroupFields"
<sqx-content-section *ngFor="let section of contentForm.sections"
[(language)]="language"
[form]="contentForm"
[formContext]="contentFormContext"
[formSection]="section"
[languages]="languages"
[schema]="schema"
[section]="section">
[schema]="schema">
</sqx-content-section>
</form>
</ng-container>

2
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();
}

2
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;
}
}

2
frontend/app/features/content/shared/references/references-editor.component.ts

@ -131,7 +131,7 @@ export class ReferencesEditorComponent extends StatefulControlComponent<State, R
this.next(s => ({ ...s, isCompact }));
}
public trackByContent(index: number, content: ContentDto) {
public trackByContent(_index: number, content: ContentDto) {
return content.id;
}
}

2
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;
}
}

2
frontend/app/features/rules/pages/rules/actions/generic-action.component.html

@ -5,7 +5,7 @@
</label>
<div class="col-9">
<sqx-control-errors [for]="property.name" [submitted]="actionFormSubmitted"></sqx-control-errors>
<sqx-control-errors [for]="property.name"></sqx-control-errors>
<ng-container [ngSwitch]="property.editor">
<ng-container *ngSwitchCase="'TextArea'">

3
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 = [];

18
frontend/app/features/rules/pages/rules/rule-wizard.component.html

@ -59,37 +59,32 @@
<ng-container *ngSwitchCase="'AssetChanged'">
<sqx-asset-changed-trigger
[trigger]="trigger"
[triggerForm]="triggerForm.form"
[triggerFormSubmitted]="triggerForm.submitted | async">
[triggerForm]="triggerForm.form">
</sqx-asset-changed-trigger>
</ng-container>
<ng-container *ngSwitchCase="'Comment'">
<sqx-comment-trigger
[trigger]="trigger"
[triggerForm]="triggerForm.form"
[triggerFormSubmitted]="triggerForm.submitted | async">
[triggerForm]="triggerForm.form">
</sqx-comment-trigger>
</ng-container>
<ng-container *ngSwitchCase="'ContentChanged'">
<sqx-content-changed-trigger
[schemas]="schemas"
[trigger]="trigger"
[triggerForm]="triggerForm.form"
[triggerFormSubmitted]="triggerForm.submitted | async">
[triggerForm]="triggerForm.form">
</sqx-content-changed-trigger>
</ng-container>
<ng-container *ngSwitchCase="'SchemaChanged'">
<sqx-schema-changed-trigger
[trigger]="trigger"
[triggerForm]="triggerForm.form"
[triggerFormSubmitted]="triggerForm.submitted | async">
[triggerForm]="triggerForm.form">
</sqx-schema-changed-trigger>
</ng-container>
<ng-container *ngSwitchCase="'Usage'">
<sqx-usage-trigger
[trigger]="trigger"
[triggerForm]="triggerForm.form"
[triggerFormSubmitted]="triggerForm.submitted | async">
[triggerForm]="triggerForm.form">
</sqx-usage-trigger>
</ng-container>
</ng-container>
@ -117,8 +112,7 @@
<sqx-generic-action
[definition]="actionElement"
[action]="action"
[actionForm]="actionForm.form"
[actionFormSubmitted]="actionForm.submitted | async">
[actionForm]="actionForm.form">
</sqx-generic-action>
</form>
</ng-container>

2
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;
}
}

2
frontend/app/features/rules/pages/rules/triggers/asset-changed-trigger.component.html

@ -2,7 +2,7 @@
<div class="form-group">
<label for="condition">Condition</label>
<sqx-control-errors for="condition" [submitted]="triggerFormSubmitted"></sqx-control-errors>
<sqx-control-errors for="condition"></sqx-control-errors>
<textarea class="form-control code" id="condition" formControlName="condition" placeholder="Optional condition as javascript expression"></textarea>
</div>

3
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 || ''));

2
frontend/app/features/rules/pages/rules/triggers/comment-trigger.component.html

@ -2,7 +2,7 @@
<div class="form-group">
<label for="condition">Condition</label>
<sqx-control-errors for="condition" [submitted]="triggerFormSubmitted"></sqx-control-errors>
<sqx-control-errors for="condition"></sqx-control-errors>
<textarea class="form-control code" id="condition" formControlName="condition" placeholder="Optional condition as javascript expression"></textarea>
</div>

3
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 || ''));

5
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<TriggerSchemaForm>;
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;
}
}

2
frontend/app/features/rules/pages/rules/triggers/schema-changed-trigger.component.html

@ -2,7 +2,7 @@
<div class="form-group">
<label for="condition">Condition</label>
<sqx-control-errors for="condition" [submitted]="triggerFormSubmitted"></sqx-control-errors>
<sqx-control-errors for="condition"></sqx-control-errors>
<textarea class="form-control code" id="condition" formControlName="condition" placeholder="Optional condition as javascript expression"></textarea>
</div>

3
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 || ''));

4
frontend/app/features/rules/pages/rules/triggers/usage-trigger.component.html

@ -2,7 +2,7 @@
<div class="form-group">
<label for="limit">Limit</label>
<sqx-control-errors for="limit" [submitted]="triggerFormSubmitted"></sqx-control-errors>
<sqx-control-errors for="limit"></sqx-control-errors>
<input type="number" step="1" class="form-control" id="limit" formControlName="limit" />
@ -14,7 +14,7 @@
<div class="form-group">
<label for="condition">Days</label>
<sqx-control-errors for="numDays" fieldName="Days" [submitted]="triggerFormSubmitted"></sqx-control-errors>
<sqx-control-errors for="numDays" fieldName="Days"></sqx-control-errors>
<input type="number" step="1" class="form-control" id="condition" formControlName="numDays" />

3
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, [

1
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';

3
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,

8
frontend/app/features/schemas/pages/schema/common/schema-edit-form.component.html

@ -1,5 +1,5 @@
<form [formGroup]="editForm.form" (ngSubmit)="saveSchema()">
<form [formGroup]="fieldForm.form" (ngSubmit)="saveSchema()">
<div class="card">
<div class="card-header">Common</div>
@ -13,7 +13,7 @@
<div class="form-group">
<label for="schemaLabel">Label</label>
<sqx-control-errors for="label" [submitted]="editForm.submitted | async"></sqx-control-errors>
<sqx-control-errors for="label"></sqx-control-errors>
<input type="text" class="form-control" id="schemaLabel" formControlName="label" />
@ -23,7 +23,7 @@
<div class="form-group">
<label for="schemaHints">Hints</label>
<sqx-control-errors for="hints" [submitted]="editForm.submitted | async"></sqx-control-errors>
<sqx-control-errors for="hints"></sqx-control-errors>
<textarea type="text" class="form-control" id="schemaHints" formControlName="hints" rows="4"></textarea>
</div>
@ -31,7 +31,7 @@
<div class="form-group">
<label for="schemaTags">Tags</label>
<sqx-control-errors for="tags" [submitted]="editForm.submitted | async"></sqx-control-errors>
<sqx-control-errors for="tags"></sqx-control-errors>
<sqx-tag-editor id="schemaTags" formControlName="tags"></sqx-tag-editor>

12
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);
});
}
}

2
frontend/app/features/schemas/pages/schema/export/schema-export-form.component.html

@ -20,7 +20,7 @@
</div>
</div>
<div class="col-auto">
<button type="submit" class="btn btn-primary" [disabled]="synchronizeForm.submitted | async">Synchronize</button>
<button type="submit" class="btn btn-primary">Synchronize</button>
</div>
</div>
</div>

9
frontend/app/features/schemas/pages/schema/fields/field-wizard.component.html

@ -37,7 +37,7 @@
</div>
<div class="form-group">
<sqx-control-errors for="name" submitOnly="true" [submitted]="addFieldForm.submitted | async"></sqx-control-errors>
<sqx-control-errors for="name" submitOnly="true"></sqx-control-errors>
<input type="text" class="form-control" formControlName="name" maxlength="40" #nameInput
placeholder="Enter field name" sqxFocusOnInit />
@ -66,10 +66,9 @@
<form [formGroup]="editForm.form" class="edit-form" (ngSubmit)="save()">
<sqx-field-form
[isEditable]="true"
[patterns]="patternsState.patterns | async"
[editForm]="editForm.form"
[editFormSubmitted]="editForm.submitted | async"
[field]="field">
[field]="field"
[fieldForm]="editForm.form"
[patterns]="patternsState.patterns | async">
</sqx-field-form>
</form>
</ng-template>

10
frontend/app/features/schemas/pages/schema/fields/field.component.html

@ -80,14 +80,12 @@
<div class="table-items-row-details" *ngIf="isEditing">
<form [formGroup]="editForm.form" (ngSubmit)="save()">
<sqx-field-form
<sqx-field-form showButtons="true"
(cancel)="toggleEditing()"
[showButtons]="true"
[patterns]="patterns"
[editForm]="editForm.form"
[editFormSubmitted]="editForm.submitted | async"
[isEditable]="isEditable"
[field]="field">
[fieldForm]="editForm.form"
[field]="field"
[isEditable]="isEditable">
</sqx-field-form>
</form>
</div>

3
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;

4
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;
}
}

6
frontend/app/features/schemas/pages/schema/fields/forms/field-form-common.component.html

@ -1,4 +1,4 @@
<div [formGroup]="editForm">
<div [formGroup]="fieldForm">
<div class="form-group row">
<label class="col-3 col-form-label" for="{{field.fieldId}}_fieldName">Name</label>
@ -15,7 +15,7 @@
<label class="col-3 col-form-label" for="{{field.fieldId}}_fieldLabel">Label</label>
<div class="col-7">
<sqx-control-errors for="label" [submitted]="editFormSubmitted"></sqx-control-errors>
<sqx-control-errors for="label"></sqx-control-errors>
<input type="text" class="form-control" id="{{field.fieldId}}_fieldLabel" maxlength="100" formControlName="label" />
@ -29,7 +29,7 @@
<label class="col-3 col-form-label" for="{{field.fieldId}}_fieldHints">Hints</label>
<div class="col-7">
<sqx-control-errors for="hints" [submitted]="editFormSubmitted"></sqx-control-errors>
<sqx-control-errors for="hints"></sqx-control-errors>
<input type="text" class="form-control" id="{{field.fieldId}}_fieldHints" maxlength="100" formControlName="hints" />

5
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;

18
frontend/app/features/schemas/pages/schema/fields/forms/field-form-ui.component.html

@ -1,29 +1,29 @@
<ng-container [ngSwitch]="field.rawProperties.fieldType">
<ng-container *ngSwitchCase="'Assets'">
<sqx-assets-ui [editForm]="editForm" [field]="field" [properties]="field.rawProperties"></sqx-assets-ui>
<sqx-assets-ui [fieldForm]="fieldForm" [field]="field" [properties]="field.rawProperties"></sqx-assets-ui>
</ng-container>
<ng-container *ngSwitchCase="'Boolean'">
<sqx-boolean-ui [editForm]="editForm" [field]="field" [properties]="field.rawProperties"></sqx-boolean-ui>
<sqx-boolean-ui [fieldForm]="fieldForm" [field]="field" [properties]="field.rawProperties"></sqx-boolean-ui>
</ng-container>
<ng-container *ngSwitchCase="'DateTime'">
<sqx-date-time-ui [editForm]="editForm" [field]="field" [properties]="field.rawProperties"></sqx-date-time-ui>
<sqx-date-time-ui [fieldForm]="fieldForm" [field]="field" [properties]="field.rawProperties"></sqx-date-time-ui>
</ng-container>
<ng-container *ngSwitchCase="'Geolocation'">
<sqx-geolocation-ui [editForm]="editForm" [field]="field" [properties]="field.rawProperties"></sqx-geolocation-ui>
<sqx-geolocation-ui [fieldForm]="fieldForm" [field]="field" [properties]="field.rawProperties"></sqx-geolocation-ui>
</ng-container>
<ng-container *ngSwitchCase="'Json'">
<sqx-json-ui [editForm]="editForm" [field]="field" [properties]="field.rawProperties"></sqx-json-ui>
<sqx-json-ui [fieldForm]="fieldForm" [field]="field" [properties]="field.rawProperties"></sqx-json-ui>
</ng-container>
<ng-container *ngSwitchCase="'Number'">
<sqx-number-ui [editForm]="editForm" [field]="field" [properties]="field.rawProperties"></sqx-number-ui>
<sqx-number-ui [fieldForm]="fieldForm" [field]="field" [properties]="field.rawProperties"></sqx-number-ui>
</ng-container>
<ng-container *ngSwitchCase="'References'">
<sqx-references-ui [editForm]="editForm" [field]="field" [properties]="field.rawProperties"></sqx-references-ui>
<sqx-references-ui [fieldForm]="fieldForm" [field]="field" [properties]="field.rawProperties"></sqx-references-ui>
</ng-container>
<ng-container *ngSwitchCase="'String'">
<sqx-string-ui [editForm]="editForm" [field]="field" [properties]="field.rawProperties"></sqx-string-ui>
<sqx-string-ui [fieldForm]="fieldForm" [field]="field" [properties]="field.rawProperties"></sqx-string-ui>
</ng-container>
<ng-container *ngSwitchCase="'Tags'">
<sqx-tags-ui [editForm]="editForm" [field]="field" [properties]="field.rawProperties"></sqx-tags-ui>
<sqx-tags-ui [fieldForm]="fieldForm" [field]="field" [properties]="field.rawProperties"></sqx-tags-ui>
</ng-container>
</ng-container>

2
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;

20
frontend/app/features/schemas/pages/schema/fields/forms/field-form-validation.component.html

@ -1,32 +1,32 @@
<ng-container [ngSwitch]="field.rawProperties.fieldType">
<ng-container *ngSwitchCase="'Array'">
<sqx-array-validation [editForm]="editForm" [field]="field" [properties]="field.rawProperties"></sqx-array-validation>
<sqx-array-validation [fieldForm]="fieldForm" [field]="field" [properties]="field.rawProperties"></sqx-array-validation>
</ng-container>
<ng-container *ngSwitchCase="'Assets'">
<sqx-assets-validation [editForm]="editForm" [field]="field" [properties]="field.rawProperties"></sqx-assets-validation>
<sqx-assets-validation [fieldForm]="fieldForm" [field]="field" [properties]="field.rawProperties"></sqx-assets-validation>
</ng-container>
<ng-container *ngSwitchCase="'DateTime'">
<sqx-date-time-validation [editForm]="editForm" [field]="field" [properties]="field.rawProperties"></sqx-date-time-validation>
<sqx-date-time-validation [fieldForm]="fieldForm" [field]="field" [properties]="field.rawProperties"></sqx-date-time-validation>
</ng-container>
<ng-container *ngSwitchCase="'Boolean'">
<sqx-boolean-validation [editForm]="editForm" [field]="field" [properties]="field.rawProperties"></sqx-boolean-validation>
<sqx-boolean-validation [fieldForm]="fieldForm" [field]="field" [properties]="field.rawProperties"></sqx-boolean-validation>
</ng-container>
<ng-container *ngSwitchCase="'Geolocation'">
<sqx-geolocation-validation [editForm]="editForm" [field]="field" [properties]="field.rawProperties"></sqx-geolocation-validation>
<sqx-geolocation-validation [fieldForm]="fieldForm" [field]="field" [properties]="field.rawProperties"></sqx-geolocation-validation>
</ng-container>
<ng-container *ngSwitchCase="'Json'">
<sqx-json-validation [editForm]="editForm" [field]="field" [properties]="field.rawProperties"></sqx-json-validation>
<sqx-json-validation [fieldForm]="fieldForm" [field]="field" [properties]="field.rawProperties"></sqx-json-validation>
</ng-container>
<ng-container *ngSwitchCase="'Number'">
<sqx-number-validation [editForm]="editForm" [field]="field" [properties]="field.rawProperties"></sqx-number-validation>
<sqx-number-validation [fieldForm]="fieldForm" [field]="field" [properties]="field.rawProperties"></sqx-number-validation>
</ng-container>
<ng-container *ngSwitchCase="'References'">
<sqx-references-validation [editForm]="editForm" [field]="field" [properties]="field.rawProperties"></sqx-references-validation>
<sqx-references-validation [fieldForm]="fieldForm" [field]="field" [properties]="field.rawProperties"></sqx-references-validation>
</ng-container>
<ng-container *ngSwitchCase="'String'">
<sqx-string-validation [editForm]="editForm" [field]="field" [properties]="field.rawProperties" [patterns]="patterns"></sqx-string-validation>
<sqx-string-validation [fieldForm]="fieldForm" [field]="field" [properties]="field.rawProperties" [patterns]="patterns"></sqx-string-validation>
</ng-container>
<ng-container *ngSwitchCase="'Tags'">
<sqx-tags-validation [editForm]="editForm" [field]="field" [properties]="field.rawProperties"></sqx-tags-validation>
<sqx-tags-validation [fieldForm]="fieldForm" [field]="field" [properties]="field.rawProperties"></sqx-tags-validation>
</ng-container>
</ng-container>

2
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;

7
frontend/app/features/schemas/pages/schema/fields/forms/field-form.component.html

@ -13,18 +13,19 @@
<div class="float-right" *ngIf="showButtons">
<button [disabled]="field.isLocked" type="reset" class="btn btn-text-secondary2" (click)="cancel.emit()">Cancel</button>
<button [disabled]="field.isLocked" type="submit" class="btn btn-primary ml-1" *ngIf="isEditable">Save</button>
</div>
</div>
<div class="table-items-row-details-tab" [class.hidden]="selectedTab !== 0">
<sqx-field-form-common [editForm]="editForm" [editFormSubmitted]="editFormSubmitted " [field]="field"></sqx-field-form-common>
<sqx-field-form-common [fieldForm]="fieldForm" [field]="field"></sqx-field-form-common>
</div>
<div class="table-items-row-details-tab" [class.hidden]="selectedTab !== 1">
<sqx-field-form-validation [patterns]="patterns" [editForm]="editForm" [field]="field"></sqx-field-form-validation>
<sqx-field-form-validation [fieldForm]="fieldForm" [field]="field" [patterns]="patterns"></sqx-field-form-validation>
</div>
<div class="table-items-row-details-tab" [class.hidden]="selectedTab !== 2">
<sqx-field-form-ui [editForm]="editForm" [field]="field"></sqx-field-form-ui>
<sqx-field-form-ui [fieldForm]="fieldForm" [field]="field"></sqx-field-form-ui>
</div>

11
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<PatternDto>;
@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();
}
}

4
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;
}
}

2
frontend/app/features/schemas/pages/schema/fields/types/array-validation.component.html

@ -1,4 +1,4 @@
<div [formGroup]="editForm">
<div [formGroup]="fieldForm">
<div class="form-group row">
<div class="col-9 offset-3">
<div class="form-check">

6
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));
}
}

2
frontend/app/features/schemas/pages/schema/fields/types/assets-ui.component.html

@ -1,5 +1,5 @@
<div [formGroup]="editForm">
<div [formGroup]="fieldForm">
<div class="form-group row">
<label class="col-3 col-form-label" for="{{field.fieldId}}_previewMode">PreviewMode</label>

6
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));
}
}

2
frontend/app/features/schemas/pages/schema/fields/types/assets-validation.component.html

@ -1,4 +1,4 @@
<div [formGroup]="editForm">
<div [formGroup]="fieldForm">
<div class="form-group row">
<div class="col-9 offset-3">
<div class="form-check">

28
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));
}
}

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save