Browse Source

Feature/default values (#597)

* Started with default values.

* More tests and equality fix.

* More tests.
pull/601/head
Sebastian Stehle 5 years ago
committed by GitHub
parent
commit
3ad0c83916
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      backend/i18n/frontend_en.json
  2. 4
      backend/i18n/frontend_it.json
  3. 4
      backend/i18n/frontend_nl.json
  4. 4
      backend/i18n/source/backend__ignore.json
  5. 4
      backend/i18n/source/frontend__ignore.json
  6. 6
      backend/i18n/source/frontend_en.json
  7. 2
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/AssetsFieldProperties.cs
  8. 2
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/BooleanFieldProperties.cs
  9. 6
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/DateTimeFieldProperties.cs
  10. 32
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/LocalizedValue.cs
  11. 6
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/NumberFieldProperties.cs
  12. 6
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/ReferencesFieldProperties.cs
  13. 14
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/StringFieldProperties.cs
  14. 6
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/TagsFieldProperties.cs
  15. 46
      backend/src/Squidex.Domain.Apps.Core.Operations/DefaultValues/DefaultValueFactory.cs
  16. 2
      backend/src/Squidex.Domain.Apps.Core.Operations/DefaultValues/DefaultValueGenerator.cs
  17. 9
      backend/src/Squidex.Infrastructure/CollectionExtensions.cs
  18. 6
      backend/src/Squidex.Infrastructure/Reflection/SimpleEquals.cs
  19. 5
      backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/AssetsFieldPropertiesDto.cs
  20. 5
      backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/BooleanFieldPropertiesDto.cs
  21. 5
      backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/DateTimeFieldPropertiesDto.cs
  22. 5
      backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/GeolocationFieldPropertiesDto.cs
  23. 5
      backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/NumberFieldPropertiesDto.cs
  24. 5
      backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/ReferencesFieldPropertiesDto.cs
  25. 5
      backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/StringFieldPropertiesDto.cs
  26. 5
      backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/TagsFieldPropertiesDto.cs
  27. 59
      backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/FieldCompareTests.cs
  28. 146
      backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/DefaultValues/DefaultValuesTests.cs
  29. 4
      backend/tests/Squidex.Infrastructure.Tests/Reflection/SimpleEqualsTests.cs
  30. 4
      frontend/app/features/content/pages/content/content-page.component.ts
  31. 4
      frontend/app/features/content/pages/contents/contents-page.component.ts
  32. 8
      frontend/app/features/schemas/pages/schema/fields/field-wizard.component.html
  33. 7
      frontend/app/features/schemas/pages/schema/fields/field-wizard.component.ts
  34. 15
      frontend/app/features/schemas/pages/schema/fields/field.component.html
  35. 9
      frontend/app/features/schemas/pages/schema/fields/field.component.ts
  36. 8
      frontend/app/features/schemas/pages/schema/fields/forms/field-form-common.component.html
  37. 4
      frontend/app/features/schemas/pages/schema/fields/forms/field-form-ui.component.html
  38. 75
      frontend/app/features/schemas/pages/schema/fields/forms/field-form-validation.component.html
  39. 8
      frontend/app/features/schemas/pages/schema/fields/forms/field-form-validation.component.ts
  40. 17
      frontend/app/features/schemas/pages/schema/fields/forms/field-form.component.html
  41. 3
      frontend/app/features/schemas/pages/schema/fields/forms/field-form.component.scss
  42. 8
      frontend/app/features/schemas/pages/schema/fields/forms/field-form.component.ts
  43. 7
      frontend/app/features/schemas/pages/schema/fields/schema-fields.component.html
  44. 5
      frontend/app/features/schemas/pages/schema/fields/schema-fields.component.ts
  45. 13
      frontend/app/features/schemas/pages/schema/fields/types/array-validation.component.html
  46. 14
      frontend/app/features/schemas/pages/schema/fields/types/array-validation.component.scss
  47. 2
      frontend/app/features/schemas/pages/schema/fields/types/assets-ui.component.html
  48. 99
      frontend/app/features/schemas/pages/schema/fields/types/assets-validation.component.html
  49. 19
      frontend/app/features/schemas/pages/schema/fields/types/assets-validation.component.scss
  50. 11
      frontend/app/features/schemas/pages/schema/fields/types/assets-validation.component.ts
  51. 2
      frontend/app/features/schemas/pages/schema/fields/types/boolean-ui.component.html
  52. 12
      frontend/app/features/schemas/pages/schema/fields/types/boolean-validation.component.html
  53. 11
      frontend/app/features/schemas/pages/schema/fields/types/boolean-validation.component.ts
  54. 2
      frontend/app/features/schemas/pages/schema/fields/types/date-time-ui.component.html
  55. 12
      frontend/app/features/schemas/pages/schema/fields/types/date-time-validation.component.html
  56. 15
      frontend/app/features/schemas/pages/schema/fields/types/date-time-validation.component.ts
  57. 4
      frontend/app/features/schemas/pages/schema/fields/types/number-ui.component.html
  58. 27
      frontend/app/features/schemas/pages/schema/fields/types/number-validation.component.html
  59. 10
      frontend/app/features/schemas/pages/schema/fields/types/number-validation.component.scss
  60. 11
      frontend/app/features/schemas/pages/schema/fields/types/number-validation.component.ts
  61. 29
      frontend/app/features/schemas/pages/schema/fields/types/references-validation.component.html
  62. 14
      frontend/app/features/schemas/pages/schema/fields/types/references-validation.component.scss
  63. 11
      frontend/app/features/schemas/pages/schema/fields/types/references-validation.component.ts
  64. 4
      frontend/app/features/schemas/pages/schema/fields/types/string-ui.component.html
  65. 61
      frontend/app/features/schemas/pages/schema/fields/types/string-validation.component.html
  66. 10
      frontend/app/features/schemas/pages/schema/fields/types/string-validation.component.scss
  67. 11
      frontend/app/features/schemas/pages/schema/fields/types/string-validation.component.ts
  68. 4
      frontend/app/features/schemas/pages/schema/fields/types/tags-ui.component.html
  69. 27
      frontend/app/features/schemas/pages/schema/fields/types/tags-validation.component.html
  70. 14
      frontend/app/features/schemas/pages/schema/fields/types/tags-validation.component.scss
  71. 11
      frontend/app/features/schemas/pages/schema/fields/types/tags-validation.component.ts
  72. 11
      frontend/app/framework/angular/forms/editors/date-time-editor.component.ts
  73. 56
      frontend/app/framework/angular/forms/editors/localized-input.component.html
  74. 0
      frontend/app/framework/angular/forms/editors/localized-input.component.scss
  75. 100
      frontend/app/framework/angular/forms/editors/localized-input.component.ts
  76. 11
      frontend/app/framework/angular/forms/editors/tag-editor.component.ts
  77. 0
      frontend/app/framework/angular/language-selector.component.html
  78. 0
      frontend/app/framework/angular/language-selector.component.scss
  79. 8
      frontend/app/framework/angular/language-selector.component.ts
  80. 2
      frontend/app/framework/declarations.ts
  81. 6
      frontend/app/framework/module.ts
  82. 2
      frontend/app/shared/components/notifo.component.scss
  83. 1
      frontend/app/shared/declarations.ts
  84. 4
      frontend/app/shared/module.ts
  85. 9
      frontend/app/shared/services/schemas.types.ts
  86. 66
      frontend/app/shared/state/contents.forms.spec.ts
  87. 29
      frontend/app/shared/state/contents.forms.ts
  88. 27
      frontend/app/shared/state/contents.forms.visitors.ts
  89. 3
      frontend/app/shared/state/languages.state.ts

6
backend/i18n/frontend_en.json

@ -626,6 +626,7 @@
"rules.wizard.selectAction": "Select Action", "rules.wizard.selectAction": "Select Action",
"rules.wizard.selectTrigger": "Select Trigger", "rules.wizard.selectTrigger": "Select Trigger",
"rules.wizard.triggerHint": "The selection of the trigger type cannot be changed later.", "rules.wizard.triggerHint": "The selection of the trigger type cannot be changed later.",
"schema.fields.localConfirmText": "Lock field",
"schemas.addField": "Add Field", "schemas.addField": "Add Field",
"schemas.addFieldAndClose": "Create and close", "schemas.addFieldAndClose": "Create and close",
"schemas.addFieldAndCreate": "Create and add field", "schemas.addFieldAndCreate": "Create and add field",
@ -659,6 +660,8 @@
"schemas.export.synchronize": "Synchronize", "schemas.export.synchronize": "Synchronize",
"schemas.field.allowedValues": "Allowed Values", "schemas.field.allowedValues": "Allowed Values",
"schemas.field.defaultValue": "Default Value", "schemas.field.defaultValue": "Default Value",
"schemas.field.defaultValues": "Default Values",
"schemas.field.defaultValuesHint": "Set the default value per language and override the default value property, if defined. Only use it if really needed.",
"schemas.field.deleteConfirmText": "Do you really want to delete the field?", "schemas.field.deleteConfirmText": "Do you really want to delete the field?",
"schemas.field.deleteConfirmTitle": "Delete field", "schemas.field.deleteConfirmTitle": "Delete field",
"schemas.field.disable": "Disable in UI", "schemas.field.disable": "Disable in UI",
@ -680,7 +683,8 @@
"schemas.field.localizableHint": "You can mark the field as localizable. It means that is dependent on the language, for example a city name.", "schemas.field.localizableHint": "You can mark the field as localizable. It means that is dependent on the language, for example a city name.",
"schemas.field.localizableMarker": "localizable", "schemas.field.localizableMarker": "localizable",
"schemas.field.lock": "Lock and prevent changes", "schemas.field.lock": "Lock and prevent changes",
"schemas.field.lockConfirmText": "WARNING: Locking a field cannot be undone! Locked field definitions cannot be unlocked, deleted, or changed anymore. Do you really want to lock this field?", "schemas.field.lockConfirmText": "WARNING: Locking a field cannot be undone! Locked field definitions cannot be unlocked, deleted, or changed anymore.\n\nDo you really want to lock this field?",
"schemas.field.lockConfirmTitle": "Lock field",
"schemas.field.lockedMarker": "Locked", "schemas.field.lockedMarker": "Locked",
"schemas.field.nameHint": "The name of the field in the API response.", "schemas.field.nameHint": "The name of the field in the API response.",
"schemas.field.namePlaceholder": "Enter field name", "schemas.field.namePlaceholder": "Enter field name",

4
backend/i18n/frontend_it.json

@ -626,6 +626,7 @@
"rules.wizard.selectAction": "Seleziona l'Azione", "rules.wizard.selectAction": "Seleziona l'Azione",
"rules.wizard.selectTrigger": "Seleziona l'Attivazione", "rules.wizard.selectTrigger": "Seleziona l'Attivazione",
"rules.wizard.triggerHint": "La selezione del tipo di attivazione non potrà essere modificata successivamente.", "rules.wizard.triggerHint": "La selezione del tipo di attivazione non potrà essere modificata successivamente.",
"schema.fields.localConfirmText": "Lock field",
"schemas.addField": "Aggiungi un Campo", "schemas.addField": "Aggiungi un Campo",
"schemas.addFieldAndClose": "Crea e chiudi", "schemas.addFieldAndClose": "Crea e chiudi",
"schemas.addFieldAndCreate": "Crea e aggiungi il campo", "schemas.addFieldAndCreate": "Crea e aggiungi il campo",
@ -659,6 +660,8 @@
"schemas.export.synchronize": "Sincronizza", "schemas.export.synchronize": "Sincronizza",
"schemas.field.allowedValues": "Valori consentiti", "schemas.field.allowedValues": "Valori consentiti",
"schemas.field.defaultValue": "Valori predefiniti", "schemas.field.defaultValue": "Valori predefiniti",
"schemas.field.defaultValues": "Default Values",
"schemas.field.defaultValuesHint": "Set the default value per language and override the default value property, if defined. Only use it if really needed.",
"schemas.field.deleteConfirmText": "Sei sicuro di voler eliminare il campo?", "schemas.field.deleteConfirmText": "Sei sicuro di voler eliminare il campo?",
"schemas.field.deleteConfirmTitle": "Cancella il campo", "schemas.field.deleteConfirmTitle": "Cancella il campo",
"schemas.field.disable": "Disabilita nella UI", "schemas.field.disable": "Disabilita nella UI",
@ -681,6 +684,7 @@
"schemas.field.localizableMarker": "consente la localizzazione", "schemas.field.localizableMarker": "consente la localizzazione",
"schemas.field.lock": "Blocca e impedisci i cambiamenti", "schemas.field.lock": "Blocca e impedisci i cambiamenti",
"schemas.field.lockConfirmText": "Attenzione: Bloccare un campo è un'azione irreversibile! Se blocchi il campo non potrai più sbloccarlo o cancellarlo o cambiarlo. Sei sicuro di voler bloccare il campo?", "schemas.field.lockConfirmText": "Attenzione: Bloccare un campo è un'azione irreversibile! Se blocchi il campo non potrai più sbloccarlo o cancellarlo o cambiarlo. Sei sicuro di voler bloccare il campo?",
"schemas.field.lockConfirmTitle": "Lock field",
"schemas.field.lockedMarker": "Bloccato", "schemas.field.lockedMarker": "Bloccato",
"schemas.field.nameHint": "Il nome del campo nelle chiamate API response.", "schemas.field.nameHint": "Il nome del campo nelle chiamate API response.",
"schemas.field.namePlaceholder": "Inserisci il nome del campo", "schemas.field.namePlaceholder": "Inserisci il nome del campo",

4
backend/i18n/frontend_nl.json

@ -626,6 +626,7 @@
"rules.wizard.selectAction": "Selecteer actie", "rules.wizard.selectAction": "Selecteer actie",
"rules.wizard.selectTrigger": "Selecteer Trigger", "rules.wizard.selectTrigger": "Selecteer Trigger",
"rules.wizard.triggerHint": "De selectie van het triggertype kan later niet worden gewijzigd.", "rules.wizard.triggerHint": "De selectie van het triggertype kan later niet worden gewijzigd.",
"schema.fields.localConfirmText": "Lock field",
"schemas.addField": "Veld toevoegen", "schemas.addField": "Veld toevoegen",
"schemas.addFieldAndClose": "Maken en sluiten", "schemas.addFieldAndClose": "Maken en sluiten",
"schemas.addFieldAndCreate": "Maak en voeg veld toe", "schemas.addFieldAndCreate": "Maak en voeg veld toe",
@ -659,6 +660,8 @@
"schemas.export.synchronize": "Synchroniseren", "schemas.export.synchronize": "Synchroniseren",
"schemas.field.allowedValues": "Toegestane waarden", "schemas.field.allowedValues": "Toegestane waarden",
"schemas.field.defaultValue": "Standaardwaarde", "schemas.field.defaultValue": "Standaardwaarde",
"schemas.field.defaultValues": "Default Values",
"schemas.field.defaultValuesHint": "Set the default value per language and override the default value property, if defined. Only use it if really needed.",
"schemas.field.deleteConfirmText": "Weet je zeker dat je het veld wilt verwijderen?", "schemas.field.deleteConfirmText": "Weet je zeker dat je het veld wilt verwijderen?",
"schemas.field.deleteConfirmTitle": "Verwijder veld", "schemas.field.deleteConfirmTitle": "Verwijder veld",
"schemas.field.disable": "Uitschakelen in gebruikersinterface", "schemas.field.disable": "Uitschakelen in gebruikersinterface",
@ -681,6 +684,7 @@
"schemas.field.localizableMarker": "localizable", "schemas.field.localizableMarker": "localizable",
"schemas.field.lock": "Vergrendel en voorkom wijzigingen", "schemas.field.lock": "Vergrendel en voorkom wijzigingen",
"schemas.field.lockConfirmText": "WAARSCHUWING: het vergrendelen van een veld kan niet ongedaan worden gemaakt! Vergrendelde velddefinities kunnen niet meer worden ontgrendeld, verwijderd of gewijzigd. Wil je dit veld echt vergrendelen?", "schemas.field.lockConfirmText": "WAARSCHUWING: het vergrendelen van een veld kan niet ongedaan worden gemaakt! Vergrendelde velddefinities kunnen niet meer worden ontgrendeld, verwijderd of gewijzigd. Wil je dit veld echt vergrendelen?",
"schemas.field.lockConfirmTitle": "Lock field",
"schemas.field.lockedMarker": "Vergrendeld", "schemas.field.lockedMarker": "Vergrendeld",
"schemas.field.nameHint": "De naam van het veld in het API-antwoord.", "schemas.field.nameHint": "De naam van het veld in het API-antwoord.",
"schemas.field.namePlaceholder": "Voer veldnaam in", "schemas.field.namePlaceholder": "Voer veldnaam in",

4
backend/i18n/source/backend__ignore.json

@ -151,10 +151,10 @@
"/Squidex.Domain.Apps.Entities/Contents/Text/Elastic/ElasticSearchTextIndex.cs": [ "/Squidex.Domain.Apps.Entities/Contents/Text/Elastic/ElasticSearchTextIndex.cs": [
"*" "*"
], ],
"/Squidex.Domain.Apps.Entities/Contents/Text/Lucene/IndexManager.cs": [ "/Squidex.Domain.Apps.Entities/Contents/Text/Lucene/IndexManager_Impl.cs": [
"*" "*"
], ],
"/Squidex.Domain.Apps.Entities/Contents/Text/Lucene/IndexManager_Impl.cs": [ "/Squidex.Domain.Apps.Entities/Contents/Text/Lucene/IndexManager.cs": [
"*" "*"
], ],
"/Squidex.Domain.Apps.Entities/Notifications/NotificationEmailSender.cs": [ "/Squidex.Domain.Apps.Entities/Notifications/NotificationEmailSender.cs": [

4
backend/i18n/source/frontend__ignore.json

@ -19,8 +19,8 @@
"#{{index + 1}}" "#{{index + 1}}"
], ],
"/features/content/shared/forms/field-editor.component.html": [ "/features/content/shared/forms/field-editor.component.html": [
"*", "{{field.displayName}} {{displaySuffix}}",
"{{field.displayName}} {{displaySuffix}}" "*"
], ],
"/features/content/shared/references/references-editor.component.html": [ "/features/content/shared/references/references-editor.component.html": [
"·" "·"

6
backend/i18n/source/frontend_en.json

@ -626,6 +626,7 @@
"rules.wizard.selectAction": "Select Action", "rules.wizard.selectAction": "Select Action",
"rules.wizard.selectTrigger": "Select Trigger", "rules.wizard.selectTrigger": "Select Trigger",
"rules.wizard.triggerHint": "The selection of the trigger type cannot be changed later.", "rules.wizard.triggerHint": "The selection of the trigger type cannot be changed later.",
"schema.fields.localConfirmText": "Lock field",
"schemas.addField": "Add Field", "schemas.addField": "Add Field",
"schemas.addFieldAndClose": "Create and close", "schemas.addFieldAndClose": "Create and close",
"schemas.addFieldAndCreate": "Create and add field", "schemas.addFieldAndCreate": "Create and add field",
@ -659,6 +660,8 @@
"schemas.export.synchronize": "Synchronize", "schemas.export.synchronize": "Synchronize",
"schemas.field.allowedValues": "Allowed Values", "schemas.field.allowedValues": "Allowed Values",
"schemas.field.defaultValue": "Default Value", "schemas.field.defaultValue": "Default Value",
"schemas.field.defaultValues": "Default Values",
"schemas.field.defaultValuesHint": "Set the default value per language and override the default value property, if defined. Only use it if really needed.",
"schemas.field.deleteConfirmText": "Do you really want to delete the field?", "schemas.field.deleteConfirmText": "Do you really want to delete the field?",
"schemas.field.deleteConfirmTitle": "Delete field", "schemas.field.deleteConfirmTitle": "Delete field",
"schemas.field.disable": "Disable in UI", "schemas.field.disable": "Disable in UI",
@ -680,7 +683,8 @@
"schemas.field.localizableHint": "You can mark the field as localizable. It means that is dependent on the language, for example a city name.", "schemas.field.localizableHint": "You can mark the field as localizable. It means that is dependent on the language, for example a city name.",
"schemas.field.localizableMarker": "localizable", "schemas.field.localizableMarker": "localizable",
"schemas.field.lock": "Lock and prevent changes", "schemas.field.lock": "Lock and prevent changes",
"schemas.field.lockConfirmText": "WARNING: Locking a field cannot be undone! Locked field definitions cannot be unlocked, deleted, or changed anymore. Do you really want to lock this field?", "schemas.field.lockConfirmText": "WARNING: Locking a field cannot be undone! Locked field definitions cannot be unlocked, deleted, or changed anymore.\n\nDo you really want to lock this field?",
"schemas.field.lockConfirmTitle": "Lock field",
"schemas.field.lockedMarker": "Locked", "schemas.field.lockedMarker": "Locked",
"schemas.field.nameHint": "The name of the field in the API response.", "schemas.field.nameHint": "The name of the field in the API response.",
"schemas.field.namePlaceholder": "Enter field name", "schemas.field.namePlaceholder": "Enter field name",

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

@ -14,6 +14,8 @@ namespace Squidex.Domain.Apps.Core.Schemas
{ {
public AssetPreviewMode PreviewMode { get; set; } public AssetPreviewMode PreviewMode { get; set; }
public LocalizedValue<string[]?> DefaultValues { get; set; }
public string[]? DefaultValue { get; set; } public string[]? DefaultValue { get; set; }
public int? MinItems { get; set; } public int? MinItems { get; set; }

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

@ -10,6 +10,8 @@ namespace Squidex.Domain.Apps.Core.Schemas
[Equals(DoNotAddEqualityOperators = true)] [Equals(DoNotAddEqualityOperators = true)]
public sealed class BooleanFieldProperties : FieldProperties public sealed class BooleanFieldProperties : FieldProperties
{ {
public LocalizedValue<bool?> DefaultValues { get; set; }
public bool? DefaultValue { get; set; } public bool? DefaultValue { get; set; }
public bool InlineEditable { get; set; } public bool InlineEditable { get; set; }

6
backend/src/Squidex.Domain.Apps.Core.Model/Schemas/DateTimeFieldProperties.cs

@ -12,12 +12,14 @@ namespace Squidex.Domain.Apps.Core.Schemas
[Equals(DoNotAddEqualityOperators = true)] [Equals(DoNotAddEqualityOperators = true)]
public sealed class DateTimeFieldProperties : FieldProperties public sealed class DateTimeFieldProperties : FieldProperties
{ {
public LocalizedValue<Instant?> DefaultValues { get; set; }
public Instant? DefaultValue { get; set; }
public Instant? MaxValue { get; set; } public Instant? MaxValue { get; set; }
public Instant? MinValue { get; set; } public Instant? MinValue { get; set; }
public Instant? DefaultValue { get; set; }
public DateTimeCalculatedDefaultValue? CalculatedDefaultValue { get; set; } public DateTimeCalculatedDefaultValue? CalculatedDefaultValue { get; set; }
public DateTimeFieldEditor Editor { get; set; } public DateTimeFieldEditor Editor { get; set; }

32
backend/src/Squidex.Domain.Apps.Core.Model/Schemas/LocalizedValue.cs

@ -0,0 +1,32 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Reflection.Equality;
namespace Squidex.Domain.Apps.Core.Schemas
{
public sealed class LocalizedValue<T> : Dictionary<string, T>, IEquatable<Dictionary<string, T>>
{
public override bool Equals(object? obj)
{
return Equals(obj as LocalizedValue<T>);
}
public bool Equals(Dictionary<string, T>? other)
{
return this.EqualsDictionary(other, EqualityComparer<string>.Default, DeepEqualityComparer<T>.Default);
}
public override int GetHashCode()
{
return this.DictionaryHashCode(EqualityComparer<string>.Default, DeepEqualityComparer<T>.Default);
}
}
}

6
backend/src/Squidex.Domain.Apps.Core.Model/Schemas/NumberFieldProperties.cs

@ -14,12 +14,14 @@ namespace Squidex.Domain.Apps.Core.Schemas
{ {
public ReadOnlyCollection<double>? AllowedValues { get; set; } public ReadOnlyCollection<double>? AllowedValues { get; set; }
public LocalizedValue<double?> DefaultValues { get; set; }
public double? DefaultValue { get; set; }
public double? MaxValue { get; set; } public double? MaxValue { get; set; }
public double? MinValue { get; set; } public double? MinValue { get; set; }
public double? DefaultValue { get; set; }
public bool IsUnique { get; set; } public bool IsUnique { get; set; }
public bool InlineEditable { get; set; } public bool InlineEditable { get; set; }

6
backend/src/Squidex.Domain.Apps.Core.Model/Schemas/ReferencesFieldProperties.cs

@ -15,6 +15,10 @@ namespace Squidex.Domain.Apps.Core.Schemas
[Equals(DoNotAddEqualityOperators = true)] [Equals(DoNotAddEqualityOperators = true)]
public sealed class ReferencesFieldProperties : FieldProperties public sealed class ReferencesFieldProperties : FieldProperties
{ {
public LocalizedValue<string[]?> DefaultValues { get; set; }
public string[]? DefaultValue { get; set; }
public int? MinItems { get; set; } public int? MinItems { get; set; }
public int? MaxItems { get; set; } public int? MaxItems { get; set; }
@ -25,8 +29,6 @@ namespace Squidex.Domain.Apps.Core.Schemas
public bool MustBePublished { get; set; } public bool MustBePublished { get; set; }
public string[]? DefaultValue { get; set; }
public ReferencesFieldEditor Editor { get; set; } public ReferencesFieldEditor Editor { get; set; }
public DomainId SchemaId public DomainId SchemaId

14
backend/src/Squidex.Domain.Apps.Core.Model/Schemas/StringFieldProperties.cs

@ -14,6 +14,14 @@ namespace Squidex.Domain.Apps.Core.Schemas
{ {
public ReadOnlyCollection<string>? AllowedValues { get; set; } public ReadOnlyCollection<string>? AllowedValues { get; set; }
public LocalizedValue<string?> DefaultValues { get; set; }
public string? DefaultValue { get; set; }
public string? Pattern { get; set; }
public string? PatternMessage { get; set; }
public int? MinLength { get; set; } public int? MinLength { get; set; }
public int? MaxLength { get; set; } public int? MaxLength { get; set; }
@ -30,12 +38,6 @@ namespace Squidex.Domain.Apps.Core.Schemas
public bool InlineEditable { get; set; } public bool InlineEditable { get; set; }
public string? DefaultValue { get; set; }
public string? Pattern { get; set; }
public string? PatternMessage { get; set; }
public StringContentType ContentType { get; set; } public StringContentType ContentType { get; set; }
public StringFieldEditor Editor { get; set; } public StringFieldEditor Editor { get; set; }

6
backend/src/Squidex.Domain.Apps.Core.Model/Schemas/TagsFieldProperties.cs

@ -14,12 +14,14 @@ namespace Squidex.Domain.Apps.Core.Schemas
{ {
public ReadOnlyCollection<string>? AllowedValues { get; set; } public ReadOnlyCollection<string>? AllowedValues { get; set; }
public LocalizedValue<string[]?> DefaultValues { get; set; }
public string[]? DefaultValue { get; set; }
public int? MinItems { get; set; } public int? MinItems { get; set; }
public int? MaxItems { get; set; } public int? MaxItems { get; set; }
public string[]? DefaultValue { get; set; }
public TagsFieldEditor Editor { get; set; } public TagsFieldEditor Editor { get; set; }
public TagsFieldNormalization Normalization { get; set; } public TagsFieldNormalization Normalization { get; set; }

46
backend/src/Squidex.Domain.Apps.Core.Operations/DefaultValues/DefaultValueFactory.cs

@ -16,17 +16,19 @@ namespace Squidex.Domain.Apps.Core.DefaultValues
public sealed class DefaultValueFactory : IFieldVisitor<IJsonValue> public sealed class DefaultValueFactory : IFieldVisitor<IJsonValue>
{ {
private readonly Instant now; private readonly Instant now;
private readonly string partition;
private DefaultValueFactory(Instant now) private DefaultValueFactory(Instant now, string partition)
{ {
this.now = now; this.now = now;
this.partition = partition;
} }
public static IJsonValue CreateDefaultValue(IField field, Instant now) public static IJsonValue CreateDefaultValue(IField field, Instant now, string partition)
{ {
Guard.NotNull(field, nameof(field)); Guard.NotNull(field, nameof(field));
return field.Accept(new DefaultValueFactory(now)); return field.Accept(new DefaultValueFactory(now, partition));
} }
public IJsonValue Visit(IArrayField field) public IJsonValue Visit(IArrayField field)
@ -36,12 +38,16 @@ namespace Squidex.Domain.Apps.Core.DefaultValues
public IJsonValue Visit(IField<AssetsFieldProperties> field) public IJsonValue Visit(IField<AssetsFieldProperties> field)
{ {
return Array(field.Properties.DefaultValue); var value = GetDefaultValue(field.Properties.DefaultValue, field.Properties.DefaultValues);
return Array(value);
} }
public IJsonValue Visit(IField<BooleanFieldProperties> field) public IJsonValue Visit(IField<BooleanFieldProperties> field)
{ {
return JsonValue.Create(field.Properties.DefaultValue); var value = GetDefaultValue(field.Properties.DefaultValue, field.Properties.DefaultValues);
return JsonValue.Create(value);
} }
public IJsonValue Visit(IField<GeolocationFieldProperties> field) public IJsonValue Visit(IField<GeolocationFieldProperties> field)
@ -56,22 +62,30 @@ namespace Squidex.Domain.Apps.Core.DefaultValues
public IJsonValue Visit(IField<NumberFieldProperties> field) public IJsonValue Visit(IField<NumberFieldProperties> field)
{ {
return JsonValue.Create(field.Properties.DefaultValue); var value = GetDefaultValue(field.Properties.DefaultValue, field.Properties.DefaultValues);
return JsonValue.Create(value);
} }
public IJsonValue Visit(IField<ReferencesFieldProperties> field) public IJsonValue Visit(IField<ReferencesFieldProperties> field)
{ {
return Array(field.Properties.DefaultValue); var value = GetDefaultValue(field.Properties.DefaultValue, field.Properties.DefaultValues);
return Array(value);
} }
public IJsonValue Visit(IField<StringFieldProperties> field) public IJsonValue Visit(IField<StringFieldProperties> field)
{ {
return JsonValue.Create(field.Properties.DefaultValue); var value = GetDefaultValue(field.Properties.DefaultValue, field.Properties.DefaultValues);
return JsonValue.Create(value);
} }
public IJsonValue Visit(IField<TagsFieldProperties> field) public IJsonValue Visit(IField<TagsFieldProperties> field)
{ {
return Array(field.Properties.DefaultValue); var value = GetDefaultValue(field.Properties.DefaultValue, field.Properties.DefaultValues);
return Array(value);
} }
public IJsonValue Visit(IField<UIFieldProperties> field) public IJsonValue Visit(IField<UIFieldProperties> field)
@ -91,7 +105,19 @@ namespace Squidex.Domain.Apps.Core.DefaultValues
return JsonValue.Create($"{now.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)}T00:00:00Z"); return JsonValue.Create($"{now.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)}T00:00:00Z");
} }
return JsonValue.Create(field.Properties.DefaultValue); var value = GetDefaultValue(field.Properties.DefaultValue, field.Properties.DefaultValues);
return JsonValue.Create(value);
}
private T GetDefaultValue<T>(T value, LocalizedValue<T>? values)
{
if (values != null && values.TryGetValue(partition, out var @default))
{
return @default;
}
return value;
} }
private static IJsonValue Array(string[]? values) private static IJsonValue Array(string[]? values)

2
backend/src/Squidex.Domain.Apps.Core.Operations/DefaultValues/DefaultValueGenerator.cs

@ -57,7 +57,7 @@ namespace Squidex.Domain.Apps.Core.DefaultValues
{ {
Guard.NotNull(fieldData, nameof(fieldData)); Guard.NotNull(fieldData, nameof(fieldData));
var defaultValue = DefaultValueFactory.CreateDefaultValue(field, SystemClock.Instance.GetCurrentInstant()); var defaultValue = DefaultValueFactory.CreateDefaultValue(field, SystemClock.Instance.GetCurrentInstant(), partitionKey);
if (field.RawProperties.IsRequired || defaultValue == null || defaultValue.Type == JsonValueType.Null) if (field.RawProperties.IsRequired || defaultValue == null || defaultValue.Type == JsonValueType.Null)
{ {

9
backend/src/Squidex.Infrastructure/CollectionExtensions.cs

@ -170,18 +170,23 @@ namespace Squidex.Infrastructure
return hashCode; return hashCode;
} }
public static bool EqualsDictionary<TKey, TValue>(this IReadOnlyDictionary<TKey, TValue> dictionary, IReadOnlyDictionary<TKey, TValue> other) where TKey : notnull public static bool EqualsDictionary<TKey, TValue>(this IReadOnlyDictionary<TKey, TValue> dictionary, IReadOnlyDictionary<TKey, TValue>? other) where TKey : notnull
{ {
return EqualsDictionary(dictionary, other, EqualityComparer<TKey>.Default, EqualityComparer<TValue>.Default); return EqualsDictionary(dictionary, other, EqualityComparer<TKey>.Default, EqualityComparer<TValue>.Default);
} }
public static bool EqualsDictionary<TKey, TValue>(this IReadOnlyDictionary<TKey, TValue> dictionary, IReadOnlyDictionary<TKey, TValue> other, IEqualityComparer<TKey> keyComparer, IEqualityComparer<TValue> valueComparer) where TKey : notnull public static bool EqualsDictionary<TKey, TValue>(this IReadOnlyDictionary<TKey, TValue> dictionary, IReadOnlyDictionary<TKey, TValue>? other, IEqualityComparer<TKey> keyComparer, IEqualityComparer<TValue> valueComparer) where TKey : notnull
{ {
if (other == null) if (other == null)
{ {
return false; return false;
} }
if (ReferenceEquals(dictionary, other))
{
return true;
}
if (dictionary.Count != other.Count) if (dictionary.Count != other.Count)
{ {
return false; return false;

6
backend/src/Squidex.Infrastructure/Reflection/SimpleEquals.cs

@ -17,7 +17,7 @@ namespace Squidex.Infrastructure.Reflection
{ {
public static class SimpleEquals public static class SimpleEquals
{ {
private static readonly ConcurrentDictionary<Type, IDeepComparer> Comparers = new ConcurrentDictionary<Type, IDeepComparer>(); private static readonly ConcurrentDictionary<Type, IDeepComparer?> Comparers = new ConcurrentDictionary<Type, IDeepComparer?>();
private static readonly HashSet<Type> SimpleTypes = new HashSet<Type>(); private static readonly HashSet<Type> SimpleTypes = new HashSet<Type>();
private static readonly DefaultComparer DefaultComparer = new DefaultComparer(); private static readonly DefaultComparer DefaultComparer = new DefaultComparer();
private static readonly NoopComparer NoopComparer = new NoopComparer(); private static readonly NoopComparer NoopComparer = new NoopComparer();
@ -38,13 +38,13 @@ namespace Squidex.Infrastructure.Reflection
return BuildCore(type) ?? NoopComparer; return BuildCore(type) ?? NoopComparer;
} }
private static IDeepComparer BuildCore(Type t) private static IDeepComparer? BuildCore(Type t)
{ {
return Comparers.GetOrAdd(t, type => return Comparers.GetOrAdd(t, type =>
{ {
if (IsSimpleType(type) || IsEquatable(type)) if (IsSimpleType(type) || IsEquatable(type))
{ {
return NoopComparer; return null;
} }
if (IsArray(type)) if (IsArray(type))

5
backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/AssetsFieldPropertiesDto.cs

@ -19,6 +19,11 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models.Fields
/// </summary> /// </summary>
public AssetPreviewMode PreviewMode { get; set; } public AssetPreviewMode PreviewMode { get; set; }
/// <summary>
/// The language specific default value as a list of asset ids.
/// </summary>
public LocalizedValue<string[]?> DefaultValues { get; set; }
/// <summary> /// <summary>
/// The default value as a list of asset ids. /// The default value as a list of asset ids.
/// </summary> /// </summary>

5
backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/BooleanFieldPropertiesDto.cs

@ -12,6 +12,11 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models.Fields
{ {
public sealed class BooleanFieldPropertiesDto : FieldPropertiesDto public sealed class BooleanFieldPropertiesDto : FieldPropertiesDto
{ {
/// <summary>
/// The language specific default value for the field value.
/// </summary>
public LocalizedValue<bool?> DefaultValues { get; set; }
/// <summary> /// <summary>
/// The default value for the field value. /// The default value for the field value.
/// </summary> /// </summary>

5
backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/DateTimeFieldPropertiesDto.cs

@ -13,6 +13,11 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models.Fields
{ {
public sealed class DateTimeFieldPropertiesDto : FieldPropertiesDto public sealed class DateTimeFieldPropertiesDto : FieldPropertiesDto
{ {
/// <summary>
/// The language specific default value for the field value.
/// </summary>
public LocalizedValue<Instant?> DefaultValues { get; set; }
/// <summary> /// <summary>
/// The default value for the field value. /// The default value for the field value.
/// </summary> /// </summary>

5
backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/GeolocationFieldPropertiesDto.cs

@ -12,11 +12,6 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models.Fields
{ {
public sealed class GeolocationFieldPropertiesDto : FieldPropertiesDto public sealed class GeolocationFieldPropertiesDto : FieldPropertiesDto
{ {
/// <summary>
/// The default value for the field value.
/// </summary>
public bool? DefaultValue { get; set; }
/// <summary> /// <summary>
/// The editor that is used to manage this field. /// The editor that is used to manage this field.
/// </summary> /// </summary>

5
backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/NumberFieldPropertiesDto.cs

@ -13,6 +13,11 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models.Fields
{ {
public sealed class NumberFieldPropertiesDto : FieldPropertiesDto public sealed class NumberFieldPropertiesDto : FieldPropertiesDto
{ {
/// <summary>
/// The language specific default value for the field value.
/// </summary>
public LocalizedValue<double?> DefaultValues { get; set; }
/// <summary> /// <summary>
/// The default value for the field value. /// The default value for the field value.
/// </summary> /// </summary>

5
backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/ReferencesFieldPropertiesDto.cs

@ -14,6 +14,11 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models.Fields
{ {
public sealed class ReferencesFieldPropertiesDto : FieldPropertiesDto public sealed class ReferencesFieldPropertiesDto : FieldPropertiesDto
{ {
/// <summary>
/// The language specific default value as a list of content ids.
/// </summary>
public LocalizedValue<string[]?> DefaultValues { get; set; }
/// <summary> /// <summary>
/// The default value as a list of content ids. /// The default value as a list of content ids.
/// </summary> /// </summary>

5
backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/StringFieldPropertiesDto.cs

@ -13,6 +13,11 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models.Fields
{ {
public sealed class StringFieldPropertiesDto : FieldPropertiesDto public sealed class StringFieldPropertiesDto : FieldPropertiesDto
{ {
/// <summary>
/// The language specific default value for the field value.
/// </summary>
public LocalizedValue<string?> DefaultValues { get; set; }
/// <summary> /// <summary>
/// The default value for the field value. /// The default value for the field value.
/// </summary> /// </summary>

5
backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/TagsFieldPropertiesDto.cs

@ -13,6 +13,11 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models.Fields
{ {
public sealed class TagsFieldPropertiesDto : FieldPropertiesDto public sealed class TagsFieldPropertiesDto : FieldPropertiesDto
{ {
/// <summary>
/// The language specific default value for the field value.
/// </summary>
public LocalizedValue<string[]?> DefaultValues { get; set; }
/// <summary> /// <summary>
/// The default value. /// The default value.
/// </summary> /// </summary>

59
backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/FieldCompareTests.cs

@ -0,0 +1,59 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Domain.Apps.Core.Schemas;
using Xunit;
namespace Squidex.Domain.Apps.Core.Model.Schemas
{
public class FieldCompareTests
{
[Fact]
public void Should_compare_two_string_fields_as_equal()
{
var lhs = new StringFieldProperties
{
DefaultValues = new LocalizedValue<string?>
{
["iv"] = "ABC"
}
};
var rhs = new StringFieldProperties
{
DefaultValues = new LocalizedValue<string?>
{
["iv"] = "ABC"
}
};
Assert.Equal(lhs, rhs);
}
[Fact]
public void Should_compare_two_tags_fields_as_equal()
{
var lhs = new TagsFieldProperties
{
DefaultValues = new LocalizedValue<string[]?>
{
["iv"] = new string[] { "A", "B", "C" }
}
};
var rhs = new TagsFieldProperties
{
DefaultValues = new LocalizedValue<string[]?>
{
["iv"] = new string[] { "A", "B", "C" }
}
};
Assert.Equal(lhs, rhs);
}
}
}

146
backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/DefaultValues/DefaultValuesTests.cs

@ -20,6 +20,7 @@ namespace Squidex.Domain.Apps.Core.Operations.DefaultValues
{ {
private readonly Instant now = Instant.FromUtc(2017, 10, 12, 16, 30, 10); private readonly Instant now = Instant.FromUtc(2017, 10, 12, 16, 30, 10);
private readonly LanguagesConfig languagesConfig = LanguagesConfig.English.Set(Language.DE); private readonly LanguagesConfig languagesConfig = LanguagesConfig.English.Set(Language.DE);
private readonly Language language = Language.DE;
private readonly Schema schema; private readonly Schema schema;
public DefaultValuesTests() public DefaultValuesTests()
@ -85,7 +86,7 @@ namespace Squidex.Domain.Apps.Core.Operations.DefaultValues
Fields.Assets(1, "1", Partitioning.Invariant, Fields.Assets(1, "1", Partitioning.Invariant,
new AssetsFieldProperties()); new AssetsFieldProperties());
Assert.Equal(JsonValue.Array(), DefaultValueFactory.CreateDefaultValue(field, now)); Assert.Equal(JsonValue.Array(), DefaultValueFactory.CreateDefaultValue(field, now, language.Iso2Code));
} }
[Fact] [Fact]
@ -95,7 +96,24 @@ namespace Squidex.Domain.Apps.Core.Operations.DefaultValues
Fields.Assets(1, "1", Partitioning.Invariant, Fields.Assets(1, "1", Partitioning.Invariant,
new AssetsFieldProperties { DefaultValue = new[] { "1", "2" } }); new AssetsFieldProperties { DefaultValue = new[] { "1", "2" } });
Assert.Equal(JsonValue.Array("1", "2"), DefaultValueFactory.CreateDefaultValue(field, now)); Assert.Equal(JsonValue.Array("1", "2"), DefaultValueFactory.CreateDefaultValue(field, now, language.Iso2Code));
}
[Fact]
public void Should_get_default_value_from_assets_field_when_localized()
{
var field =
Fields.Assets(1, "1", Partitioning.Invariant,
new AssetsFieldProperties
{
DefaultValues = new LocalizedValue<string[]?>
{
[language.Iso2Code] = null
},
DefaultValue = new[] { "1", "2" }
});
Assert.Equal(JsonValue.Array(), DefaultValueFactory.CreateDefaultValue(field, now, language.Iso2Code));
} }
[Fact] [Fact]
@ -105,7 +123,24 @@ namespace Squidex.Domain.Apps.Core.Operations.DefaultValues
Fields.Boolean(1, "1", Partitioning.Invariant, Fields.Boolean(1, "1", Partitioning.Invariant,
new BooleanFieldProperties { DefaultValue = true }); new BooleanFieldProperties { DefaultValue = true });
Assert.Equal(JsonValue.True, DefaultValueFactory.CreateDefaultValue(field, now)); Assert.Equal(JsonValue.True, DefaultValueFactory.CreateDefaultValue(field, now, language.Iso2Code));
}
[Fact]
public void Should_get_default_value_from_boolean_field_when_localized()
{
var field =
Fields.Boolean(1, "1", Partitioning.Invariant,
new BooleanFieldProperties
{
DefaultValues = new LocalizedValue<bool?>
{
[language.Iso2Code] = null
},
DefaultValue = true
});
Assert.Equal(JsonValue.Null, DefaultValueFactory.CreateDefaultValue(field, now, language.Iso2Code));
} }
[Fact] [Fact]
@ -115,7 +150,7 @@ namespace Squidex.Domain.Apps.Core.Operations.DefaultValues
Fields.DateTime(1, "1", Partitioning.Invariant, Fields.DateTime(1, "1", Partitioning.Invariant,
new DateTimeFieldProperties { DefaultValue = FutureDays(15) }); new DateTimeFieldProperties { DefaultValue = FutureDays(15) });
Assert.Equal(JsonValue.Create(FutureDays(15)), DefaultValueFactory.CreateDefaultValue(field, now)); Assert.Equal(JsonValue.Create(FutureDays(15)), DefaultValueFactory.CreateDefaultValue(field, now, language.Iso2Code));
} }
[Fact] [Fact]
@ -125,7 +160,7 @@ namespace Squidex.Domain.Apps.Core.Operations.DefaultValues
Fields.DateTime(1, "1", Partitioning.Invariant, Fields.DateTime(1, "1", Partitioning.Invariant,
new DateTimeFieldProperties { CalculatedDefaultValue = DateTimeCalculatedDefaultValue.Today }); new DateTimeFieldProperties { CalculatedDefaultValue = DateTimeCalculatedDefaultValue.Today });
Assert.Equal(JsonValue.Create("2017-10-12T00:00:00Z"), DefaultValueFactory.CreateDefaultValue(field, now)); Assert.Equal(JsonValue.Create("2017-10-12T00:00:00Z"), DefaultValueFactory.CreateDefaultValue(field, now, language.Iso2Code));
} }
[Fact] [Fact]
@ -135,7 +170,24 @@ namespace Squidex.Domain.Apps.Core.Operations.DefaultValues
Fields.DateTime(1, "1", Partitioning.Invariant, Fields.DateTime(1, "1", Partitioning.Invariant,
new DateTimeFieldProperties { CalculatedDefaultValue = DateTimeCalculatedDefaultValue.Now }); new DateTimeFieldProperties { CalculatedDefaultValue = DateTimeCalculatedDefaultValue.Now });
Assert.Equal(JsonValue.Create("2017-10-12T16:30:10Z"), DefaultValueFactory.CreateDefaultValue(field, now)); Assert.Equal(JsonValue.Create("2017-10-12T16:30:10Z"), DefaultValueFactory.CreateDefaultValue(field, now, language.Iso2Code));
}
[Fact]
public void Should_get_default_value_from_datetime_field_when_localized()
{
var field =
Fields.DateTime(1, "1", Partitioning.Invariant,
new DateTimeFieldProperties
{
DefaultValues = new LocalizedValue<Instant?>
{
[language.Iso2Code] = null
},
DefaultValue = FutureDays(15)
});
Assert.Equal(JsonValue.Null, DefaultValueFactory.CreateDefaultValue(field, now, language.Iso2Code));
} }
[Fact] [Fact]
@ -145,7 +197,7 @@ namespace Squidex.Domain.Apps.Core.Operations.DefaultValues
Fields.Json(1, "1", Partitioning.Invariant, Fields.Json(1, "1", Partitioning.Invariant,
new JsonFieldProperties()); new JsonFieldProperties());
Assert.Equal(JsonValue.Null, DefaultValueFactory.CreateDefaultValue(field, now)); Assert.Equal(JsonValue.Null, DefaultValueFactory.CreateDefaultValue(field, now, language.Iso2Code));
} }
[Fact] [Fact]
@ -155,7 +207,7 @@ namespace Squidex.Domain.Apps.Core.Operations.DefaultValues
Fields.Geolocation(1, "1", Partitioning.Invariant, Fields.Geolocation(1, "1", Partitioning.Invariant,
new GeolocationFieldProperties()); new GeolocationFieldProperties());
Assert.Equal(JsonValue.Null, DefaultValueFactory.CreateDefaultValue(field, now)); Assert.Equal(JsonValue.Null, DefaultValueFactory.CreateDefaultValue(field, now, language.Iso2Code));
} }
[Fact] [Fact]
@ -165,7 +217,24 @@ namespace Squidex.Domain.Apps.Core.Operations.DefaultValues
Fields.Number(1, "1", Partitioning.Invariant, Fields.Number(1, "1", Partitioning.Invariant,
new NumberFieldProperties { DefaultValue = 12 }); new NumberFieldProperties { DefaultValue = 12 });
Assert.Equal(JsonValue.Create(12), DefaultValueFactory.CreateDefaultValue(field, now)); Assert.Equal(JsonValue.Create(12), DefaultValueFactory.CreateDefaultValue(field, now, language.Iso2Code));
}
[Fact]
public void Should_get_default_value_from_number_field_when_localized()
{
var field =
Fields.Number(1, "1", Partitioning.Invariant,
new NumberFieldProperties
{
DefaultValues = new LocalizedValue<double?>
{
[language.Iso2Code] = null
},
DefaultValue = 12
});
Assert.Equal(JsonValue.Null, DefaultValueFactory.CreateDefaultValue(field, now, language.Iso2Code));
} }
[Fact] [Fact]
@ -175,7 +244,7 @@ namespace Squidex.Domain.Apps.Core.Operations.DefaultValues
Fields.References(1, "1", Partitioning.Invariant, Fields.References(1, "1", Partitioning.Invariant,
new ReferencesFieldProperties()); new ReferencesFieldProperties());
Assert.Equal(JsonValue.Array(), DefaultValueFactory.CreateDefaultValue(field, now)); Assert.Equal(JsonValue.Array(), DefaultValueFactory.CreateDefaultValue(field, now, language.Iso2Code));
} }
[Fact] [Fact]
@ -185,7 +254,24 @@ namespace Squidex.Domain.Apps.Core.Operations.DefaultValues
Fields.References(1, "1", Partitioning.Invariant, Fields.References(1, "1", Partitioning.Invariant,
new ReferencesFieldProperties { DefaultValue = new[] { "1", "2" } }); new ReferencesFieldProperties { DefaultValue = new[] { "1", "2" } });
Assert.Equal(JsonValue.Array("1", "2"), DefaultValueFactory.CreateDefaultValue(field, now)); Assert.Equal(JsonValue.Array("1", "2"), DefaultValueFactory.CreateDefaultValue(field, now, language.Iso2Code));
}
[Fact]
public void Should_get_default_value_from_references_field_when_localized()
{
var field =
Fields.References(1, "1", Partitioning.Invariant,
new ReferencesFieldProperties
{
DefaultValues = new LocalizedValue<string[]?>
{
[language.Iso2Code] = null
},
DefaultValue = new[] { "1", "2" }
});
Assert.Equal(JsonValue.Array(), DefaultValueFactory.CreateDefaultValue(field, now, language.Iso2Code));
} }
[Fact] [Fact]
@ -195,7 +281,24 @@ namespace Squidex.Domain.Apps.Core.Operations.DefaultValues
Fields.String(1, "1", Partitioning.Invariant, Fields.String(1, "1", Partitioning.Invariant,
new StringFieldProperties { DefaultValue = "default" }); new StringFieldProperties { DefaultValue = "default" });
Assert.Equal(JsonValue.Create("default"), DefaultValueFactory.CreateDefaultValue(field, now)); Assert.Equal(JsonValue.Create("default"), DefaultValueFactory.CreateDefaultValue(field, now, language.Iso2Code));
}
[Fact]
public void Should_get_default_value_from_string_field_when_localized()
{
var field =
Fields.String(1, "1", Partitioning.Invariant,
new StringFieldProperties
{
DefaultValues = new LocalizedValue<string?>
{
[language.Iso2Code] = null
},
DefaultValue = "default"
});
Assert.Equal(JsonValue.Null, DefaultValueFactory.CreateDefaultValue(field, now, language.Iso2Code));
} }
[Fact] [Fact]
@ -205,7 +308,24 @@ namespace Squidex.Domain.Apps.Core.Operations.DefaultValues
Fields.Tags(1, "1", Partitioning.Invariant, Fields.Tags(1, "1", Partitioning.Invariant,
new TagsFieldProperties { DefaultValue = new[] { "tag1", "tag2" } }); new TagsFieldProperties { DefaultValue = new[] { "tag1", "tag2" } });
Assert.Equal(JsonValue.Array("tag1", "tag2"), DefaultValueFactory.CreateDefaultValue(field, now)); Assert.Equal(JsonValue.Array("tag1", "tag2"), DefaultValueFactory.CreateDefaultValue(field, now, language.Iso2Code));
}
[Fact]
public void Should_get_default_value_from_tags_field_when_localized()
{
var field =
Fields.Tags(1, "1", Partitioning.Invariant,
new TagsFieldProperties
{
DefaultValues = new LocalizedValue<string[]?>
{
[language.Iso2Code] = null
},
DefaultValue = new[] { "tag1", "tag2" }
});
Assert.Equal(JsonValue.Array(), DefaultValueFactory.CreateDefaultValue(field, now, language.Iso2Code));
} }
private Instant FutureDays(int days) private Instant FutureDays(int days)

4
backend/tests/Squidex.Infrastructure.Tests/Reflection/SimpleEqualsTests.cs

@ -9,6 +9,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using Squidex.Infrastructure.Collections; using Squidex.Infrastructure.Collections;
using Squidex.Infrastructure.Reflection.Equality;
using Xunit; using Xunit;
#pragma warning disable CA1822 // Mark members as static #pragma warning disable CA1822 // Mark members as static
@ -98,7 +99,10 @@ namespace Squidex.Infrastructure.Reflection
public void Should_compare_values(object lhs, object rhs) public void Should_compare_values(object lhs, object rhs)
{ {
Assert.True(SimpleEquals.IsEquals(lhs, lhs)); Assert.True(SimpleEquals.IsEquals(lhs, lhs));
Assert.True(DeepEqualityComparer<object>.Default.Equals(lhs, lhs));
Assert.False(SimpleEquals.IsEquals(lhs, rhs)); Assert.False(SimpleEquals.IsEquals(lhs, rhs));
Assert.True(DeepEqualityComparer<object>.Default.Equals(lhs, rhs));
} }
[Fact] [Fact]

4
frontend/app/features/content/pages/content/content-page.component.ts

@ -63,9 +63,9 @@ export class ContentPageComponent extends ResourceOwner implements CanComponentD
this.contentsState.loadIfNotLoaded(); this.contentsState.loadIfNotLoaded();
this.own( this.own(
this.languagesState.languages this.languagesState.languagesDtos
.subscribe(languages => { .subscribe(languages => {
this.languages = languages.map(x => x.language); this.languages = languages;
this.language = this.languages.find(x => x.isMaster)!; this.language = this.languages.find(x => x.isMaster)!;
})); }));

4
frontend/app/features/content/pages/contents/contents-page.component.ts

@ -94,9 +94,9 @@ export class ContentsPageComponent extends ResourceOwner implements OnInit {
})); }));
this.own( this.own(
this.languagesState.languages this.languagesState.languagesDtos
.subscribe(languages => { .subscribe(languages => {
this.languages = languages.map(x => x.language); this.languages = languages;
this.language = this.languages.find(x => x.isMaster)!; this.language = this.languages.find(x => x.isMaster)!;
this.languageMaster = this.language; this.languageMaster = this.language;
})); }));

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

@ -63,7 +63,13 @@
<ng-template #notEditing> <ng-template #notEditing>
<form [formGroup]="editForm.form" class="edit-form" (ngSubmit)="save()"> <form [formGroup]="editForm.form" class="edit-form" (ngSubmit)="save()">
<sqx-field-form [isEditable]="true" [field]="field" [fieldForm]="editForm.form" [patterns]="patternsState.patterns | async"> <sqx-field-form
[patterns]="patternsState.patterns | async"
[languages]="languagesState.languagesDtos | async"
[field]="field"
[fieldForm]="editForm.form"
[isEditable]="true"
[isLocalizable]="isLocalizable">
</sqx-field-form> </sqx-field-form>
</form> </form>
</ng-template> </ng-template>

7
frontend/app/features/schemas/pages/schema/fields/field-wizard.component.ts

@ -7,7 +7,7 @@
import { Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core'; import { Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core';
import { FormBuilder } from '@angular/forms'; import { FormBuilder } from '@angular/forms';
import { AddFieldForm, createProperties, EditFieldForm, FieldDto, fieldTypes, PatternsState, RootFieldDto, SchemaDetailsDto, SchemasState, Types } from '@app/shared'; import { AddFieldForm, createProperties, EditFieldForm, FieldDto, fieldTypes, LanguagesState, PatternsState, RootFieldDto, SchemaDetailsDto, SchemasState, Types } from '@app/shared';
const DEFAULT_FIELD = { name: '', partitioning: 'invariant', properties: createProperties('String') }; const DEFAULT_FIELD = { name: '', partitioning: 'invariant', properties: createProperties('String') };
@ -29,6 +29,10 @@ export class FieldWizardComponent implements OnInit {
@Output() @Output()
public complete = new EventEmitter(); public complete = new EventEmitter();
public get isLocalizable() {
return (this.parent && this.parent.isLocalizable) || this.field['isLocalizable'];
}
public fieldTypes = fieldTypes; public fieldTypes = fieldTypes;
public field: FieldDto; public field: FieldDto;
@ -40,6 +44,7 @@ export class FieldWizardComponent implements OnInit {
constructor( constructor(
private readonly formBuilder: FormBuilder, private readonly formBuilder: FormBuilder,
private readonly schemasState: SchemasState, private readonly schemasState: SchemasState,
public readonly languagesState: LanguagesState,
public readonly patternsState: PatternsState public readonly patternsState: PatternsState
) {} ) {}

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

@ -67,7 +67,7 @@
<a class="dropdown-item" <a class="dropdown-item"
(sqxConfirmClick)="lockField()" (sqxConfirmClick)="lockField()"
confirmTitle="i18n:schemas.field.lockConfirmText" confirmTitle="i18n:schemas.field.lockConfirmTitle"
confirmText="i18n:schemas.field.lockConfirmText" confirmText="i18n:schemas.field.lockConfirmText"
confirmRememberKey="lockField"> confirmRememberKey="lockField">
{{ 'schemas.field.lock' | sqxTranslate }} {{ 'schemas.field.lock' | sqxTranslate }}
@ -94,7 +94,13 @@
<div class="table-items-row-details" *ngIf="isEditing"> <div class="table-items-row-details" *ngIf="isEditing">
<form [formGroup]="editForm.form" (ngSubmit)="save()"> <form [formGroup]="editForm.form" (ngSubmit)="save()">
<sqx-field-form showButtons="true" (cancel)="toggleEditing()" [patterns]="patterns" [fieldForm]="editForm.form" [field]="field" [isEditable]="isEditable"> <sqx-field-form showButtons="true" (cancel)="toggleEditing()"
[patterns]="patterns"
[languages]="languages"
[fieldForm]="editForm.form"
[field]="field"
[isEditable]="isEditable"
[isLocalizable]="isLocalizable">
</sqx-field-form> </sqx-field-form>
</form> </form>
</div> </div>
@ -112,7 +118,7 @@
<span class="nested-field-line-h"></span> <span class="nested-field-line-h"></span>
<sqx-field [field]="nested" [schema]="schema" [parent]="field" [patterns]="patterns"> <sqx-field [field]="nested" [schema]="schema" [parent]="field" [patterns]="patterns" [languages]="languages">
<i cdkDragHandle class="icon-drag2 drag-handle"></i> <i cdkDragHandle class="icon-drag2 drag-handle"></i>
</sqx-field> </sqx-field>
</div> </div>
@ -127,7 +133,8 @@
</div> </div>
<ng-container *sqxModal="addFieldDialog"> <ng-container *sqxModal="addFieldDialog">
<sqx-field-wizard [schema]="schema" [parent]="field" (complete)="addFieldDialog.hide()"> <sqx-field-wizard
[schema]="schema" [parent]="field" (complete)="addFieldDialog.hide()">
</sqx-field-wizard> </sqx-field-wizard>
</ng-container> </ng-container>
</ng-container> </ng-container>

9
frontend/app/features/schemas/pages/schema/fields/field.component.ts

@ -8,7 +8,7 @@
import { CdkDragDrop } from '@angular/cdk/drag-drop'; import { CdkDragDrop } from '@angular/cdk/drag-drop';
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core'; import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
import { FormBuilder } from '@angular/forms'; import { FormBuilder } from '@angular/forms';
import { createProperties, DialogModel, EditFieldForm, fadeAnimation, ModalModel, NestedFieldDto, PatternDto, RootFieldDto, SchemaDetailsDto, SchemasState, sorted } from '@app/shared'; import { createProperties, DialogModel, EditFieldForm, fadeAnimation, LanguageDto, ModalModel, NestedFieldDto, PatternDto, RootFieldDto, SchemaDetailsDto, SchemasState, sorted } from '@app/shared';
@Component({ @Component({
selector: 'sqx-field', selector: 'sqx-field',
@ -28,9 +28,16 @@ export class FieldComponent implements OnChanges {
@Input() @Input()
public parent: RootFieldDto; public parent: RootFieldDto;
@Input()
public languages: ReadonlyArray<LanguageDto>;
@Input() @Input()
public patterns: ReadonlyArray<PatternDto>; public patterns: ReadonlyArray<PatternDto>;
public get isLocalizable() {
return (this.parent && this.parent.isLocalizable) || this.field['isLocalizable'];
}
public dropdown = new ModalModel(); public dropdown = new ModalModel();
public trackByFieldFn: (_index: number, field: NestedFieldDto) => any; public trackByFieldFn: (_index: number, field: NestedFieldDto) => any;

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

@ -2,7 +2,7 @@
<div class="form-group row"> <div class="form-group row">
<label class="col-3 col-form-label" for="{{field.fieldId}}_fieldName">{{ 'common.name' | sqxTranslate }}</label> <label class="col-3 col-form-label" for="{{field.fieldId}}_fieldName">{{ 'common.name' | sqxTranslate }}</label>
<div class="col-7"> <div class="col-9">
<input type="text" class="form-control" id="{{field.fieldId}}_fieldName" readonly [ngModel]="field.name" [ngModelOptions]="standalone"> <input type="text" class="form-control" id="{{field.fieldId}}_fieldName" readonly [ngModel]="field.name" [ngModelOptions]="standalone">
<sqx-form-hint> <sqx-form-hint>
@ -14,7 +14,7 @@
<div class="form-group row"> <div class="form-group row">
<label class="col-3 col-form-label" for="{{field.fieldId}}_fieldLabel">{{ 'common.label' | sqxTranslate }}</label> <label class="col-3 col-form-label" for="{{field.fieldId}}_fieldLabel">{{ 'common.label' | sqxTranslate }}</label>
<div class="col-7"> <div class="col-9">
<sqx-control-errors for="label"></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"> <input type="text" class="form-control" id="{{field.fieldId}}_fieldLabel" maxlength="100" formControlName="label">
@ -28,7 +28,7 @@
<div class="form-group row"> <div class="form-group row">
<label class="col-3 col-form-label" for="{{field.fieldId}}_fieldHints">{{ 'common.hints' | sqxTranslate }}</label> <label class="col-3 col-form-label" for="{{field.fieldId}}_fieldHints">{{ 'common.hints' | sqxTranslate }}</label>
<div class="col-7"> <div class="col-9">
<sqx-control-errors for="hints"></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"> <input type="text" class="form-control" id="{{field.fieldId}}_fieldHints" maxlength="100" formControlName="hints">
@ -42,7 +42,7 @@
<div class="form-group row" *ngIf="field.properties.isContentField"> <div class="form-group row" *ngIf="field.properties.isContentField">
<label class="col-3 col-form-label">{{ 'common.tags' | sqxTranslate }}</label> <label class="col-3 col-form-label">{{ 'common.tags' | sqxTranslate }}</label>
<div class="col-7"> <div class="col-9">
<sqx-tag-editor id="schemaTags" formControlName="tags"></sqx-tag-editor> <sqx-tag-editor id="schemaTags" formControlName="tags"></sqx-tag-editor>
<sqx-form-hint> <sqx-form-hint>

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

@ -2,7 +2,7 @@
<div class="form-group row mb-3"> <div class="form-group row mb-3">
<label class="col-3 col-form-label" for="{{field.fieldId}}_editorUrl">{{ 'schemas.field.editorUrl' | sqxTranslate }}</label> <label class="col-3 col-form-label" for="{{field.fieldId}}_editorUrl">{{ 'schemas.field.editorUrl' | sqxTranslate }}</label>
<div class="col-6"> <div class="col-9">
<input type="url" class="form-control" id="{{field.fieldId}}_editorUrl" formControlName="editorUrl"> <input type="url" class="form-control" id="{{field.fieldId}}_editorUrl" formControlName="editorUrl">
<sqx-form-hint> <sqx-form-hint>
@ -44,7 +44,7 @@
<div [formGroup]="fieldForm"> <div [formGroup]="fieldForm">
<div class="form-group row mt-3"> <div class="form-group row mt-3">
<div class="col-6 offset-3"> <div class="col-9 offset-3">
<div class="custom-control custom-checkbox"> <div class="custom-control custom-checkbox">
<input class="custom-control-input" type="checkbox" id="{{field.fieldId}}_fieldHalfWidth" formControlName="isHalfWidth"> <input class="custom-control-input" type="checkbox" id="{{field.fieldId}}_fieldHalfWidth" formControlName="isHalfWidth">
<label class="custom-control-label" for="{{field.fieldId}}_fieldHalfWidth"> <label class="custom-control-label" for="{{field.fieldId}}_fieldHalfWidth">

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

@ -24,33 +24,88 @@
<ng-container [ngSwitch]="field.rawProperties.fieldType"> <ng-container [ngSwitch]="field.rawProperties.fieldType">
<ng-container *ngSwitchCase="'Array'"> <ng-container *ngSwitchCase="'Array'">
<sqx-array-validation [fieldForm]="fieldForm" [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>
<ng-container *ngSwitchCase="'Assets'"> <ng-container *ngSwitchCase="'Assets'">
<sqx-assets-validation [fieldForm]="fieldForm" [field]="field" [properties]="field.rawProperties"></sqx-assets-validation> <sqx-assets-validation
[languages]="languages"
[isLocalizable]="isLocalizable"
[fieldForm]="fieldForm"
[field]="field"
[properties]="field.rawProperties">
</sqx-assets-validation>
</ng-container> </ng-container>
<ng-container *ngSwitchCase="'DateTime'"> <ng-container *ngSwitchCase="'DateTime'">
<sqx-date-time-validation [fieldForm]="fieldForm" [field]="field" [properties]="field.rawProperties"></sqx-date-time-validation> <sqx-date-time-validation
[languages]="languages"
[isLocalizable]="isLocalizable"
[fieldForm]="fieldForm"
[field]="field"
[properties]="field.rawProperties">
</sqx-date-time-validation>
</ng-container> </ng-container>
<ng-container *ngSwitchCase="'Boolean'"> <ng-container *ngSwitchCase="'Boolean'">
<sqx-boolean-validation [fieldForm]="fieldForm" [field]="field" [properties]="field.rawProperties"></sqx-boolean-validation> <sqx-boolean-validation
[languages]="languages"
[isLocalizable]="isLocalizable"
[fieldForm]="fieldForm"
[field]="field"
[properties]="field.rawProperties">
</sqx-boolean-validation>
</ng-container> </ng-container>
<ng-container *ngSwitchCase="'Geolocation'"> <ng-container *ngSwitchCase="'Geolocation'">
<sqx-geolocation-validation [fieldForm]="fieldForm" [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>
<ng-container *ngSwitchCase="'Json'"> <ng-container *ngSwitchCase="'Json'">
<sqx-json-validation [fieldForm]="fieldForm" [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>
<ng-container *ngSwitchCase="'Number'"> <ng-container *ngSwitchCase="'Number'">
<sqx-number-validation [fieldForm]="fieldForm" [field]="field" [properties]="field.rawProperties"></sqx-number-validation> <sqx-number-validation
[languages]="languages"
[isLocalizable]="isLocalizable"
[fieldForm]="fieldForm"
[field]="field"
[properties]="field.rawProperties">
</sqx-number-validation>
</ng-container> </ng-container>
<ng-container *ngSwitchCase="'References'"> <ng-container *ngSwitchCase="'References'">
<sqx-references-validation [fieldForm]="fieldForm" [field]="field" [properties]="field.rawProperties"></sqx-references-validation> <sqx-references-validation
[languages]="languages"
[isLocalizable]="isLocalizable"
[fieldForm]="fieldForm"
[field]="field"
[properties]="field.rawProperties">
</sqx-references-validation>
</ng-container> </ng-container>
<ng-container *ngSwitchCase="'String'"> <ng-container *ngSwitchCase="'String'">
<sqx-string-validation [fieldForm]="fieldForm" [field]="field" [properties]="field.rawProperties" [patterns]="patterns"></sqx-string-validation> <sqx-string-validation
[languages]="languages"
[isLocalizable]="isLocalizable"
[fieldForm]="fieldForm"
[field]="field"
[properties]="field.rawProperties"
[patterns]="patterns">
</sqx-string-validation>
</ng-container> </ng-container>
<ng-container *ngSwitchCase="'Tags'"> <ng-container *ngSwitchCase="'Tags'">
<sqx-tags-validation [fieldForm]="fieldForm" [field]="field" [properties]="field.rawProperties"></sqx-tags-validation> <sqx-tags-validation
[languages]="languages"
[isLocalizable]="isLocalizable"
[fieldForm]="fieldForm"
[field]="field"
[properties]="field.rawProperties">
</sqx-tags-validation>
</ng-container> </ng-container>
</ng-container> </ng-container>

8
frontend/app/features/schemas/pages/schema/fields/forms/field-form-validation.component.ts

@ -7,7 +7,7 @@
import { Component, Input } from '@angular/core'; import { Component, Input } from '@angular/core';
import { FormGroup } from '@angular/forms'; import { FormGroup } from '@angular/forms';
import { FieldDto, PatternDto } from '@app/shared'; import { FieldDto, LanguageDto, PatternDto } from '@app/shared';
@Component({ @Component({
selector: 'sqx-field-form-validation', selector: 'sqx-field-form-validation',
@ -23,4 +23,10 @@ export class FieldFormValidationComponent {
@Input() @Input()
public patterns: ReadonlyArray<PatternDto>; public patterns: ReadonlyArray<PatternDto>;
@Input()
public languages: ReadonlyArray<LanguageDto>;
@Input()
public isLocalizable: boolean;
} }

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

@ -23,13 +23,24 @@
</div> </div>
<div class="table-items-row-details-tab" [class.hidden]="selectedTab !== 0"> <div class="table-items-row-details-tab" [class.hidden]="selectedTab !== 0">
<sqx-field-form-common [fieldForm]="fieldForm" [field]="field"></sqx-field-form-common> <sqx-field-form-common
[fieldForm]="fieldForm"
[field]="field">
</sqx-field-form-common>
</div> </div>
<div class="table-items-row-details-tab" [class.hidden]="selectedTab !== 1"> <div class="table-items-row-details-tab" [class.hidden]="selectedTab !== 1">
<sqx-field-form-validation [fieldForm]="fieldForm" [field]="field" [patterns]="patterns"></sqx-field-form-validation> <sqx-field-form-validation
[patterns]="patterns" [languages]="languages"
[fieldForm]="fieldForm"
[field]="field"
[isLocalizable]="isLocalizable">
</sqx-field-form-validation>
</div> </div>
<div class="table-items-row-details-tab" [class.hidden]="selectedTab !== 2"> <div class="table-items-row-details-tab" [class.hidden]="selectedTab !== 2">
<sqx-field-form-ui [fieldForm]="fieldForm" [field]="field"></sqx-field-form-ui> <sqx-field-form-ui
[fieldForm]="fieldForm"
[field]="field">
</sqx-field-form-ui>
</div> </div>

3
frontend/app/features/schemas/pages/schema/fields/forms/field-form.component.scss

@ -0,0 +1,3 @@
.table-items-row-details-tab {
padding-right: 3rem;
}

8
frontend/app/features/schemas/pages/schema/fields/forms/field-form.component.ts

@ -7,7 +7,7 @@
import { AfterViewInit, Component, EventEmitter, Input, Output } from '@angular/core'; import { AfterViewInit, Component, EventEmitter, Input, Output } from '@angular/core';
import { FormGroup } from '@angular/forms'; import { FormGroup } from '@angular/forms';
import { FieldDto, PatternDto } from '@app/shared'; import { FieldDto, LanguageDto, PatternDto } from '@app/shared';
@Component({ @Component({
selector: 'sqx-field-form', selector: 'sqx-field-form',
@ -30,6 +30,12 @@ export class FieldFormComponent implements AfterViewInit {
@Input() @Input()
public patterns: ReadonlyArray<PatternDto>; public patterns: ReadonlyArray<PatternDto>;
@Input()
public languages: ReadonlyArray<LanguageDto>;
@Input()
public isLocalizable: boolean;
@Output() @Output()
public cancel = new EventEmitter(); public cancel = new EventEmitter();

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

@ -6,6 +6,7 @@
</button> </button>
</div> </div>
<ng-container *ngIf="languageState.languagesDtos | async; let languages">
<ng-container *ngIf="patternsState.patterns | async; let patterns"> <ng-container *ngIf="patternsState.patterns | async; let patterns">
<div <div
cdkDropList cdkDropList
@ -13,7 +14,7 @@
[cdkDropListData]="schema.fields" [cdkDropListData]="schema.fields"
(cdkDropListDropped)="sortFields($event)"> (cdkDropListDropped)="sortFields($event)">
<div *ngFor="let field of schema.fields; trackBy: trackByFieldFn" class="table-drag" cdkDrag cdkDragLockAxis="y"> <div *ngFor="let field of schema.fields; trackBy: trackByFieldFn" class="table-drag" cdkDrag cdkDragLockAxis="y">
<sqx-field [field]="field" [schema]="schema" [patterns]="patterns"> <sqx-field [field]="field" [schema]="schema" [patterns]="patterns" [languages]="languages">
<i cdkDragHandle class="icon-drag2 drag-handle"></i> <i cdkDragHandle class="icon-drag2 drag-handle"></i>
</sqx-field> </sqx-field>
</div> </div>
@ -23,8 +24,10 @@
<i class="icon icon-plus field-button-icon"></i> <div class="field-button-text">{{ 'schemas.addFieldButton' | sqxTranslate }}</div> <i class="icon icon-plus field-button-icon"></i> <div class="field-button-text">{{ 'schemas.addFieldButton' | sqxTranslate }}</div>
</button> </button>
</ng-container> </ng-container>
</ng-container>
<ng-container *sqxModal="addFieldDialog"> <ng-container *sqxModal="addFieldDialog">
<sqx-field-wizard [schema]="schema" (complete)="addFieldDialog.hide()"> <sqx-field-wizard
[schema]="schema" (complete)="addFieldDialog.hide()">
</sqx-field-wizard> </sqx-field-wizard>
</ng-container> </ng-container>

5
frontend/app/features/schemas/pages/schema/fields/schema-fields.component.ts

@ -7,7 +7,7 @@
import { CdkDragDrop } from '@angular/cdk/drag-drop'; import { CdkDragDrop } from '@angular/cdk/drag-drop';
import { Component, Input, OnInit } from '@angular/core'; import { Component, Input, OnInit } from '@angular/core';
import { DialogModel, FieldDto, fieldTypes, PatternsState, SchemaDetailsDto, SchemasState, sorted } from '@app/shared'; import { DialogModel, FieldDto, fieldTypes, LanguagesState, PatternsState, SchemaDetailsDto, SchemasState, sorted } from '@app/shared';
@Component({ @Component({
selector: 'sqx-schema-fields', selector: 'sqx-schema-fields',
@ -26,6 +26,7 @@ export class SchemaFieldsComponent implements OnInit {
constructor( constructor(
public readonly schemasState: SchemasState, public readonly schemasState: SchemasState,
public readonly languageState: LanguagesState,
public readonly patternsState: PatternsState public readonly patternsState: PatternsState
) { ) {
this.trackByFieldFn = this.trackByField.bind(this); this.trackByFieldFn = this.trackByField.bind(this);
@ -33,6 +34,8 @@ export class SchemaFieldsComponent implements OnInit {
public ngOnInit() { public ngOnInit() {
this.patternsState.load(); this.patternsState.load();
this.languageState.load();
} }
public sortFields(event: CdkDragDrop<ReadonlyArray<FieldDto>>) { public sortFields(event: CdkDragDrop<ReadonlyArray<FieldDto>>) {

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

@ -2,13 +2,18 @@
<div class="form-group row"> <div class="form-group row">
<label class="col-3 col-form-label">{{ 'schemas.fieldTypes.array.count' | sqxTranslate }}</label> <label class="col-3 col-form-label">{{ 'schemas.fieldTypes.array.count' | sqxTranslate }}</label>
<div class="col-3 minmax-col"> <div class="col-9">
<div class="row no-gutters">
<div class="col">
<input type="number" class="form-control" formControlName="minItems" placeholder="{{ 'schemas.fieldTypes.array.countMin' | sqxTranslate }}"> <input type="number" class="form-control" formControlName="minItems" placeholder="{{ 'schemas.fieldTypes.array.countMin' | sqxTranslate }}">
<label class="col-form-label minmax-label">-</label>
</div> </div>
<div class="col-3"> <div class="col-auto">
<label class="col-form-label minmax">-</label>
</div>
<div class="col">
<input type="number" class="form-control" formControlName="maxItems" placeholder="{{ 'schemas.fieldTypes.array.countMax' | sqxTranslate }}"> <input type="number" class="form-control" formControlName="maxItems" placeholder="{{ 'schemas.fieldTypes.array.countMax' | sqxTranslate }}">
</div> </div>
</div> </div>
</div> </div>
</div>
</div>

14
frontend/app/features/schemas/pages/schema/fields/types/array-validation.component.scss

@ -1,13 +1,5 @@
.minmax { .minmax {
&-col { text-align: center;
position: relative; text-decoration: none;
} width: 2rem;
&-label {
@include absolute(0, -.2rem, auto, auto);
}
}
.form-group {
margin-top: .5rem;
} }

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

@ -3,7 +3,7 @@
<div class="form-group row"> <div class="form-group row">
<label class="col-3 col-form-label" for="{{field.fieldId}}_previewMode">{{ 'schemas.fieldTypes.assets.previewMode' | sqxTranslate }}</label> <label class="col-3 col-form-label" for="{{field.fieldId}}_previewMode">{{ 'schemas.fieldTypes.assets.previewMode' | sqxTranslate }}</label>
<div class="col-6"> <div class="col-9">
<select class="custom-select" id="{{field.fieldId}}_previewMode" formControlName="previewMode"> <select class="custom-select" id="{{field.fieldId}}_previewMode" formControlName="previewMode">
<option value="ImageAndFileName">{{ 'schemas.fieldTypes.assets.previewImageAndFileName' | sqxTranslate }}</option> <option value="ImageAndFileName">{{ 'schemas.fieldTypes.assets.previewImageAndFileName' | sqxTranslate }}</option>
<option value="Image">{{ 'schemas.fieldTypes.assets.previewImage' | sqxTranslate }}</option> <option value="Image">{{ 'schemas.fieldTypes.assets.previewImage' | sqxTranslate }}</option>

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

@ -2,29 +2,41 @@
<div class="form-group row"> <div class="form-group row">
<label class="col-3 col-form-label">{{ 'schemas.fieldTypes.assets.count' | sqxTranslate }}</label> <label class="col-3 col-form-label">{{ 'schemas.fieldTypes.assets.count' | sqxTranslate }}</label>
<div class="col-3 minmax-col"> <div class="col-9">
<div class="row no-gutters">
<div class="col">
<input type="number" class="form-control" formControlName="minItems" placeholder="{{ 'schemas.fieldTypes.assets.countMin' | sqxTranslate }}"> <input type="number" class="form-control" formControlName="minItems" placeholder="{{ 'schemas.fieldTypes.assets.countMin' | sqxTranslate }}">
<label class="col-form-label minmax-label">-</label>
</div> </div>
<div class="col-3"> <div class="col-auto">
<label class="col-form-label minmax">-</label>
</div>
<div class="col">
<input type="number" class="form-control" formControlName="maxItems" placeholder="{{ 'schemas.fieldTypes.assets.countMax' | sqxTranslate }}"> <input type="number" class="form-control" formControlName="maxItems" placeholder="{{ 'schemas.fieldTypes.assets.countMax' | sqxTranslate }}">
</div> </div>
<div class="col col-label">
</div>
</div>
</div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<label class="col-3 col-form-label">{{ 'schemas.fieldTypes.assets.size' | sqxTranslate }}</label> <label class="col-3 col-form-label">{{ 'schemas.fieldTypes.assets.size' | sqxTranslate }}</label>
<div class="col-3 minmax-col"> <div class="col-9">
<div class="row no-gutters">
<div class="col">
<input type="number" class="form-control" formControlName="minSize" placeholder="{{ 'schemas.fieldTypes.assets.sizeMin' | sqxTranslate }}"> <input type="number" class="form-control" formControlName="minSize" placeholder="{{ 'schemas.fieldTypes.assets.sizeMin' | sqxTranslate }}">
<label class="col-form-label minmax-label">-</label>
</div> </div>
<div class="col-3"> <div class="col-auto">
<label class="col-form-label minmax">-</label>
</div>
<div class="col">
<input type="number" class="form-control" formControlName="maxSize" placeholder="{{ 'schemas.fieldTypes.assets.sizeMax' | sqxTranslate }}"> <input type="number" class="form-control" formControlName="maxSize" placeholder="{{ 'schemas.fieldTypes.assets.sizeMax' | sqxTranslate }}">
</div> </div>
<div class="col-3"> <div class="col col-label">
<label class="col-form-label">{{ 'common.bytes' | sqxTranslate }}</label> <label class="col-form-label">bytes</label>
</div>
</div>
</div> </div>
</div> </div>
@ -42,50 +54,65 @@
<div class="form-group row"> <div class="form-group row">
<label class="col-3 col-form-label">{{ 'common.width' | sqxTranslate }}</label> <label class="col-3 col-form-label">{{ 'common.width' | sqxTranslate }}</label>
<div class="col-2 minmax-col"> <div class="col-9">
<div class="row no-gutters">
<div class="col">
<input type="number" class="form-control" formControlName="minWidth"> <input type="number" class="form-control" formControlName="minWidth">
<label class="col-form-label minmax-label">-</label>
</div> </div>
<div class="col-2"> <div class="col-auto">
<label class="col-form-label minmax">-</label>
</div>
<div class="col">
<input type="number" class="form-control" formControlName="maxWidth"> <input type="number" class="form-control" formControlName="maxWidth">
</div> </div>
<div class="col-2"> <div class="col col-label">
<label class="col-form-label">px</label> <label class="col-form-label">px</label>
</div> </div>
</div> </div>
</div>
</div>
<div class="form-group row"> <div class="form-group row">
<label class="col-3 col-form-label">{{ 'common.height' | sqxTranslate }}</label> <label class="col-3 col-form-label">{{ 'common.height' | sqxTranslate }}</label>
<div class="col-2 minmax-col"> <div class="col-9">
<div class="row no-gutters">
<div class="col">
<input type="number" class="form-control" formControlName="minHeight"> <input type="number" class="form-control" formControlName="minHeight">
<label class="col-form-label minmax-label">-</label>
</div> </div>
<div class="col-2"> <div class="col-auto">
<label class="col-form-label minmax">-</label>
</div>
<div class="col">
<input type="number" class="form-control" formControlName="maxHeight"> <input type="number" class="form-control" formControlName="maxHeight">
</div> </div>
<div class="col-2"> <div class="col col-label">
<label class="col-form-label">px</label> <label class="col-form-label">px</label>
</div> </div>
</div> </div>
</div>
</div>
<div class="form-group row"> <div class="form-group row">
<label class="col-3 col-form-label">{{ 'common.aspectRatio' | sqxTranslate }}</label> <label class="col-3 col-form-label">{{ 'common.aspectRatio' | sqxTranslate }}</label>
<div class="col-2 minmax-col"> <div class="col-9">
<input type="number" class="form-control" formControlName="aspectWidth" placeholder="4"> <div class="row no-gutters">
<div class="col">
<label class="col-form-label minmax-label">:</label> <input type="number" class="form-control" formControlName="aspectWidth">
</div> </div>
<div class="col-2"> <div class="col-auto">
<input type="number" class="form-control" formControlName="aspectHeight" placeholder="3"> <label class="col-form-label minmax">:</label>
</div> </div>
<div class="col-2"> <div class="col">
<input type="number" class="form-control" formControlName="aspectHeight">
</div>
<div class="col col-label">
<label class="col-form-label">px</label> <label class="col-form-label">px</label>
</div> </div>
</div> </div>
</div>
</div>
<div class="form-group row"> <div class="form-group row">
<div class="col-9 offset-3"> <div class="col-9 offset-3">
@ -98,12 +125,12 @@
</div> </div>
</div> </div>
<div class="form-group2 row"> <div class="form-group row">
<label class="col-3 col-form-label"> <label class="col-3 col-form-label">
{{ 'schemas.fieldTypes.assets.fileExtensions' | sqxTranslate }} {{ 'schemas.fieldTypes.assets.fileExtensions' | sqxTranslate }}
</label> </label>
<div class="col-6"> <div class="col-9">
<sqx-tag-editor formControlName="allowedExtensions"></sqx-tag-editor> <sqx-tag-editor formControlName="allowedExtensions"></sqx-tag-editor>
</div> </div>
</div> </div>
@ -111,8 +138,20 @@
<div class="form-group row"> <div class="form-group row">
<label class="col-3 col-form-label">{{ 'schemas.field.defaultValue' | sqxTranslate }}</label> <label class="col-3 col-form-label">{{ 'schemas.field.defaultValue' | sqxTranslate }}</label>
<div class="col-6"> <div class="col-9">
<sqx-tag-editor formControlName="defaultValue"></sqx-tag-editor> <sqx-tag-editor formControlName="defaultValue"></sqx-tag-editor>
</div> </div>
</div> </div>
<div class="form-group row" *ngIf="isLocalizable">
<label class="col-3 col-form-label">{{ 'schemas.field.defaultValues' | sqxTranslate }}</label>
<div class="col-9">
<sqx-localized-input type="tags" [languages]="languages" formControlName="defaultValues"></sqx-localized-input>
<sqx-form-hint>
{{ 'schemas.field.defaultValuesHint' | sqxTranslate }}
</sqx-form-hint>
</div>
</div>
</div> </div>

19
frontend/app/features/schemas/pages/schema/fields/types/assets-validation.component.scss

@ -1,17 +1,10 @@
.minmax { .minmax {
&-col { text-align: center;
position: relative; text-decoration: none;
width: 2rem;
} }
&-label { .col-label {
@include absolute(0, -.2rem, auto, auto); @include force-width(5rem);
} padding-left: .5rem;
}
.form-group {
margin-top: .5rem;
}
.form-group2 {
margin-top: 3rem;
} }

11
frontend/app/features/schemas/pages/schema/fields/types/assets-validation.component.ts

@ -7,7 +7,7 @@
import { Component, Input, OnInit } from '@angular/core'; import { Component, Input, OnInit } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms'; import { FormControl, FormGroup } from '@angular/forms';
import { AssetsFieldPropertiesDto, FieldDto } from '@app/shared'; import { AssetsFieldPropertiesDto, FieldDto, LanguageDto } from '@app/shared';
@Component({ @Component({
selector: 'sqx-assets-validation', selector: 'sqx-assets-validation',
@ -24,6 +24,12 @@ export class AssetsValidationComponent implements OnInit {
@Input() @Input()
public properties: AssetsFieldPropertiesDto; public properties: AssetsFieldPropertiesDto;
@Input()
public languages: ReadonlyArray<LanguageDto>;
@Input()
public isLocalizable: boolean;
public ngOnInit() { public ngOnInit() {
this.fieldForm.setControl('minItems', this.fieldForm.setControl('minItems',
new FormControl(this.properties.minItems)); new FormControl(this.properties.minItems));
@ -66,5 +72,8 @@ export class AssetsValidationComponent implements OnInit {
this.fieldForm.setControl('defaultValue', this.fieldForm.setControl('defaultValue',
new FormControl(this.properties.defaultValue)); new FormControl(this.properties.defaultValue));
this.fieldForm.setControl('defaultValues',
new FormControl(this.properties.defaultValues));
} }
} }

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

@ -2,7 +2,7 @@
<div class="form-group row"> <div class="form-group row">
<label class="col-3 col-form-label" for="{{field.fieldId}}_fieldPlaceholder">{{ 'schemas.field.placeholder' | sqxTranslate }}</label> <label class="col-3 col-form-label" for="{{field.fieldId}}_fieldPlaceholder">{{ 'schemas.field.placeholder' | sqxTranslate }}</label>
<div class="col-6"> <div class="col-9">
<input type="text" class="form-control" id="{{field.fieldId}}_fieldPlaceholder" maxlength="100" formControlName="placeholder"> <input type="text" class="form-control" id="{{field.fieldId}}_fieldPlaceholder" maxlength="100" formControlName="placeholder">
<sqx-form-hint> <sqx-form-hint>

12
frontend/app/features/schemas/pages/schema/fields/types/boolean-validation.component.html

@ -9,4 +9,16 @@
</div> </div>
</div> </div>
</div> </div>
<div class="form-group row" *ngIf="isLocalizable">
<label class="col-3 col-form-label" for="{{field.fieldId}}_fieldDefaultValues">{{ 'schemas.field.defaultValues' | sqxTranslate }}</label>
<div class="col-9">
<sqx-localized-input type="boolean" [languages]="languages" formControlName="defaultValues" id="{{field.fieldId}}_fieldDefaultValues"></sqx-localized-input>
<sqx-form-hint>
{{ 'schemas.field.defaultValuesHint' | sqxTranslate }}
</sqx-form-hint>
</div>
</div>
</div> </div>

11
frontend/app/features/schemas/pages/schema/fields/types/boolean-validation.component.ts

@ -7,7 +7,7 @@
import { Component, Input, OnInit } from '@angular/core'; import { Component, Input, OnInit } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms'; import { FormControl, FormGroup } from '@angular/forms';
import { BooleanFieldPropertiesDto, FieldDto, hasNoValue$ } from '@app/shared'; import { BooleanFieldPropertiesDto, FieldDto, hasNoValue$, LanguageDto } from '@app/shared';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
@Component({ @Component({
@ -25,12 +25,21 @@ export class BooleanValidationComponent implements OnInit {
@Input() @Input()
public properties: BooleanFieldPropertiesDto; public properties: BooleanFieldPropertiesDto;
@Input()
public languages: ReadonlyArray<LanguageDto>;
@Input()
public isLocalizable: boolean;
public showDefaultValue: Observable<boolean>; public showDefaultValue: Observable<boolean>;
public ngOnInit() { public ngOnInit() {
this.fieldForm.setControl('defaultValue', this.fieldForm.setControl('defaultValue',
new FormControl(this.properties.defaultValue)); new FormControl(this.properties.defaultValue));
this.fieldForm.setControl('defaultValues',
new FormControl(this.properties.defaultValues));
this.fieldForm.setControl('inlineEditable', this.fieldForm.setControl('inlineEditable',
new FormControl(this.properties.inlineEditable)); new FormControl(this.properties.inlineEditable));

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

@ -2,7 +2,7 @@
<div class="form-group row"> <div class="form-group row">
<label class="col-3 col-form-label" for="{{field.fieldId}}_fieldPlaceholder">Placeholder</label> <label class="col-3 col-form-label" for="{{field.fieldId}}_fieldPlaceholder">Placeholder</label>
<div class="col-6"> <div class="col-9">
<input type="text" class="form-control" id="{{field.fieldId}}_fieldPlaceholder" maxlength="100" formControlName="placeholder"> <input type="text" class="form-control" id="{{field.fieldId}}_fieldPlaceholder" maxlength="100" formControlName="placeholder">
<sqx-form-hint> <sqx-form-hint>

12
frontend/app/features/schemas/pages/schema/fields/types/date-time-validation.component.html

@ -34,5 +34,17 @@
<sqx-date-time-editor enforceTime="true" mode="DateTime" formControlName="defaultValue"></sqx-date-time-editor> <sqx-date-time-editor enforceTime="true" mode="DateTime" formControlName="defaultValue"></sqx-date-time-editor>
</div> </div>
</div> </div>
<div class="form-group row" *ngIf="isLocalizable">
<label class="col-3 col-form-label" for="{{field.fieldId}}_fieldDefaultValues">{{ 'schemas.field.defaultValues' | sqxTranslate }}</label>
<div class="col-9">
<sqx-localized-input type="datetime" [languages]="languages" formControlName="defaultValues" id="{{field.fieldId}}_fieldDefaultValues"></sqx-localized-input>
<sqx-form-hint>
{{ 'schemas.field.defaultValuesHint' | sqxTranslate }}
</sqx-form-hint>
</div>
</div>
</ng-container> </ng-container>
</div> </div>

15
frontend/app/features/schemas/pages/schema/fields/types/date-time-validation.component.ts

@ -7,7 +7,7 @@
import { Component, Input, OnInit } from '@angular/core'; import { Component, Input, OnInit } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms'; import { FormControl, FormGroup } from '@angular/forms';
import { DateTimeFieldPropertiesDto, FieldDto, hasNoValue$, ValidatorsEx } from '@app/shared'; import { DateTimeFieldPropertiesDto, FieldDto, hasNoValue$, LanguageDto, ValidatorsEx } from '@app/shared';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
@Component({ @Component({
@ -25,6 +25,12 @@ export class DateTimeValidationComponent implements OnInit {
@Input() @Input()
public properties: DateTimeFieldPropertiesDto; public properties: DateTimeFieldPropertiesDto;
@Input()
public languages: ReadonlyArray<LanguageDto>;
@Input()
public isLocalizable: boolean;
public showDefaultValues: Observable<boolean>; public showDefaultValues: Observable<boolean>;
public showDefaultValue: Observable<boolean>; public showDefaultValue: Observable<boolean>;
@ -45,9 +51,10 @@ export class DateTimeValidationComponent implements OnInit {
])); ]));
this.fieldForm.setControl('defaultValue', this.fieldForm.setControl('defaultValue',
new FormControl(this.properties.defaultValue, [ new FormControl(this.properties.defaultValue));
ValidatorsEx.validDateTime()
])); this.fieldForm.setControl('defaultValues',
new FormControl(this.properties.defaultValues));
this.showDefaultValues = this.showDefaultValues =
hasNoValue$(this.fieldForm.controls['isRequired']); hasNoValue$(this.fieldForm.controls['isRequired']);

4
frontend/app/features/schemas/pages/schema/fields/types/number-ui.component.html

@ -2,7 +2,7 @@
<div class="form-group row"> <div class="form-group row">
<label class="col-3 col-form-label" for="{{field.fieldId}}_fieldPlaceholder">{{ 'schemas.field.placeholder' | sqxTranslate }}</label> <label class="col-3 col-form-label" for="{{field.fieldId}}_fieldPlaceholder">{{ 'schemas.field.placeholder' | sqxTranslate }}</label>
<div class="col-6"> <div class="col-9">
<input type="text" class="form-control" id="{{field.fieldId}}_fieldPlaceholder" maxlength="100" formControlName="placeholder"> <input type="text" class="form-control" id="{{field.fieldId}}_fieldPlaceholder" maxlength="100" formControlName="placeholder">
<sqx-form-hint> <sqx-form-hint>
@ -26,7 +26,7 @@
<div class="form-group row" [class.hidden]="hideAllowedValues | async"> <div class="form-group row" [class.hidden]="hideAllowedValues | async">
<label class="col-3 col-form-label">{{ 'schemas.field.allowedValues' | sqxTranslate }}</label> <label class="col-3 col-form-label">{{ 'schemas.field.allowedValues' | sqxTranslate }}</label>
<div class="col-6"> <div class="col-9">
<sqx-tag-editor formControlName="allowedValues" [converter]="converter"></sqx-tag-editor> <sqx-tag-editor formControlName="allowedValues" [converter]="converter"></sqx-tag-editor>
</div> </div>
</div> </div>

27
frontend/app/features/schemas/pages/schema/fields/types/number-validation.component.html

@ -13,21 +13,38 @@
<div class="form-group row"> <div class="form-group row">
<label class="col-3 col-form-label">{{ 'schemas.fieldTypes.number.range' | sqxTranslate }}</label> <label class="col-3 col-form-label">{{ 'schemas.fieldTypes.number.range' | sqxTranslate }}</label>
<div class="col-3 minmax-col"> <div class="col-9">
<div class="row no-gutters">
<div class="col">
<input type="number" class="form-control" formControlName="minValue" placeholder="{{ 'schemas.fieldTypes.number.rangeMin' | sqxTranslate }}"> <input type="number" class="form-control" formControlName="minValue" placeholder="{{ 'schemas.fieldTypes.number.rangeMin' | sqxTranslate }}">
<label class="col-form-label minmax-label">-</label>
</div> </div>
<div class="col-3"> <div class="col-auto">
<label class="col-form-label minmax">-</label>
</div>
<div class="col">
<input type="number" class="form-control" formControlName="maxValue" placeholder="{{ 'schemas.fieldTypes.number.rangeMax' | sqxTranslate }}"> <input type="number" class="form-control" formControlName="maxValue" placeholder="{{ 'schemas.fieldTypes.number.rangeMax' | sqxTranslate }}">
</div> </div>
</div> </div>
</div>
</div>
<div class="form-group row"> <div class="form-group row">
<label class="col-3 col-form-label" for="{{field.fieldId}}_fieldDefaultValue">{{ 'schemas.field.defaultValue' | sqxTranslate }}</label> <label class="col-3 col-form-label" for="{{field.fieldId}}_fieldDefaultValue">{{ 'schemas.field.defaultValue' | sqxTranslate }}</label>
<div class="col-6"> <div class="col-9">
<input type="number" class="form-control" id="{{field.fieldId}}_fieldDefaultValue" formControlName="defaultValue"> <input type="number" class="form-control" id="{{field.fieldId}}_fieldDefaultValue" formControlName="defaultValue">
</div> </div>
</div> </div>
<div class="form-group row" *ngIf="isLocalizable">
<label class="col-3 col-form-label" for="{{field.fieldId}}_fieldDefaultValues">{{ 'schemas.field.defaultValues' | sqxTranslate }}</label>
<div class="col-9">
<sqx-localized-input type="number" [languages]="languages" formControlName="defaultValues" id="{{field.fieldId}}_fieldDefaultValues"></sqx-localized-input>
<sqx-form-hint>
{{ 'schemas.field.defaultValuesHint' | sqxTranslate }}
</sqx-form-hint>
</div>
</div>
</div> </div>

10
frontend/app/features/schemas/pages/schema/fields/types/number-validation.component.scss

@ -1,9 +1,5 @@
.minmax { .minmax {
&-col { text-align: center;
position: relative; text-decoration: none;
} width: 2rem;
&-label {
@include absolute(0, -2rem, auto, auto);
}
} }

11
frontend/app/features/schemas/pages/schema/fields/types/number-validation.component.ts

@ -7,7 +7,7 @@
import { Component, Input, OnInit } from '@angular/core'; import { Component, Input, OnInit } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms'; import { FormControl, FormGroup } from '@angular/forms';
import { FieldDto, NumberFieldPropertiesDto, RootFieldDto, Types } from '@app/shared'; import { FieldDto, LanguageDto, NumberFieldPropertiesDto, RootFieldDto, Types } from '@app/shared';
@Component({ @Component({
selector: 'sqx-number-validation', selector: 'sqx-number-validation',
@ -24,6 +24,12 @@ export class NumberValidationComponent implements OnInit {
@Input() @Input()
public properties: NumberFieldPropertiesDto; public properties: NumberFieldPropertiesDto;
@Input()
public languages: ReadonlyArray<LanguageDto>;
@Input()
public isLocalizable: boolean;
public showUnique: boolean; public showUnique: boolean;
public ngOnInit() { public ngOnInit() {
@ -42,5 +48,8 @@ export class NumberValidationComponent implements OnInit {
this.fieldForm.setControl('defaultValue', this.fieldForm.setControl('defaultValue',
new FormControl(this.properties.defaultValue)); new FormControl(this.properties.defaultValue));
this.fieldForm.setControl('defaultValues',
new FormControl(this.properties.defaultValues));
} }
} }

29
frontend/app/features/schemas/pages/schema/fields/types/references-validation.component.html

@ -2,7 +2,7 @@
<div class="form-group row"> <div class="form-group row">
<label class="col-3 col-form-label" for="{{field.fieldId}}_fieldSchemaId">{{ 'common.schemas' | sqxTranslate }}</label> <label class="col-3 col-form-label" for="{{field.fieldId}}_fieldSchemaId">{{ 'common.schemas' | sqxTranslate }}</label>
<div class="col-6"> <div class="col-9">
<sqx-tag-editor placeholder="{{ 'common.tagAddSchema' | sqxTranslate }}" formControlName="schemaIds" <sqx-tag-editor placeholder="{{ 'common.tagAddSchema' | sqxTranslate }}" formControlName="schemaIds"
[converter]="schemasSource.converter | async" [suggestions]="(schemasSource.converter | async)?.suggestions"> [converter]="schemasSource.converter | async" [suggestions]="(schemasSource.converter | async)?.suggestions">
</sqx-tag-editor> </sqx-tag-editor>
@ -23,15 +23,20 @@
<div class="form-group row"> <div class="form-group row">
<label class="col-3 col-form-label">{{ 'schemas.fieldTypes.references.count' | sqxTranslate }}</label> <label class="col-3 col-form-label">{{ 'schemas.fieldTypes.references.count' | sqxTranslate }}</label>
<div class="col-3 minmax-col"> <div class="col-9">
<div class="row">
<div class="col-6">
<input type="number" class="form-control" formControlName="minItems" placeholder="{{ 'schemas.fieldTypes.references.countMin' | sqxTranslate }}"> <input type="number" class="form-control" formControlName="minItems" placeholder="{{ 'schemas.fieldTypes.references.countMin' | sqxTranslate }}">
<label class="col-form-label minmax-label">-</label>
</div> </div>
<div class="col-3"> <div class="col-auto">
<label class="col-form-label minmax">-</label>
</div>
<div class="col-6">
<input type="number" class="form-control" formControlName="maxItems" placeholder="{{ 'schemas.fieldTypes.references.countMax' | sqxTranslate }}"> <input type="number" class="form-control" formControlName="maxItems" placeholder="{{ 'schemas.fieldTypes.references.countMax' | sqxTranslate }}">
</div> </div>
</div> </div>
</div>
</div>
<div class="form-group row mb-3"> <div class="form-group row mb-3">
<div class="col-9 offset-3"> <div class="col-9 offset-3">
@ -47,9 +52,21 @@
<div class="form-group row"> <div class="form-group row">
<label class="col-3 col-form-label">{{ 'schemas.field.defaultValue' | sqxTranslate }}</label> <label class="col-3 col-form-label">{{ 'schemas.field.defaultValue' | sqxTranslate }}</label>
<div class="col-6"> <div class="col-9">
<sqx-tag-editor formControlName="defaultValue"></sqx-tag-editor> <sqx-tag-editor formControlName="defaultValue"></sqx-tag-editor>
</div> </div>
</div> </div>
<div class="form-group row" *ngIf="isLocalizable">
<label class="col-3 col-form-label">{{ 'schemas.field.defaultValues' | sqxTranslate }}</label>
<div class="col-9">
<sqx-localized-input type="tags" [languages]="languages" formControlName="defaultValues"></sqx-localized-input>
<sqx-form-hint>
{{ 'schemas.field.defaultValuesHint' | sqxTranslate }}
</sqx-form-hint>
</div>
</div>
</div> </div>

14
frontend/app/features/schemas/pages/schema/fields/types/references-validation.component.scss

@ -1,13 +1,5 @@
.minmax { .minmax {
&-col { text-align: center;
position: relative; text-decoration: none;
} width: 2rem;
&-label {
@include absolute(0, -.2rem, auto, auto);
}
}
.form-group {
margin-top: .5rem;
} }

11
frontend/app/features/schemas/pages/schema/fields/types/references-validation.component.ts

@ -7,7 +7,7 @@
import { Component, Input, OnInit } from '@angular/core'; import { Component, Input, OnInit } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms'; import { FormControl, FormGroup } from '@angular/forms';
import { FieldDto, ReferencesFieldPropertiesDto, SchemaTagSource } from '@app/shared'; import { FieldDto, LanguageDto, ReferencesFieldPropertiesDto, SchemaTagSource } from '@app/shared';
@Component({ @Component({
selector: 'sqx-references-validation', selector: 'sqx-references-validation',
@ -24,6 +24,12 @@ export class ReferencesValidationComponent implements OnInit {
@Input() @Input()
public properties: ReferencesFieldPropertiesDto; public properties: ReferencesFieldPropertiesDto;
@Input()
public languages: ReadonlyArray<LanguageDto>;
@Input()
public isLocalizable: boolean;
constructor( constructor(
public readonly schemasSource: SchemaTagSource public readonly schemasSource: SchemaTagSource
) { ) {
@ -45,6 +51,9 @@ export class ReferencesValidationComponent implements OnInit {
this.fieldForm.setControl('defaultValue', this.fieldForm.setControl('defaultValue',
new FormControl(this.properties.defaultValue)); new FormControl(this.properties.defaultValue));
this.fieldForm.setControl('defaultValues',
new FormControl(this.properties.defaultValues));
this.fieldForm.setControl('mustBePublished', this.fieldForm.setControl('mustBePublished',
new FormControl(this.properties.mustBePublished)); new FormControl(this.properties.mustBePublished));
} }

4
frontend/app/features/schemas/pages/schema/fields/types/string-ui.component.html

@ -2,7 +2,7 @@
<div class="form-group row"> <div class="form-group row">
<label class="col-3 col-form-label" for="{{field.fieldId}}_fieldPlaceholder">{{ 'schemas.field.placeholder' | sqxTranslate }}</label> <label class="col-3 col-form-label" for="{{field.fieldId}}_fieldPlaceholder">{{ 'schemas.field.placeholder' | sqxTranslate }}</label>
<div class="col-6"> <div class="col-9">
<input type="text" class="form-control" id="{{field.fieldId}}_fieldPlaceholder" maxlength="100" formControlName="placeholder"> <input type="text" class="form-control" id="{{field.fieldId}}_fieldPlaceholder" maxlength="100" formControlName="placeholder">
<sqx-form-hint> <sqx-form-hint>
@ -89,7 +89,7 @@
<div class="form-group row" [class.hidden]="hideAllowedValues | async"> <div class="form-group row" [class.hidden]="hideAllowedValues | async">
<label class="col-3 col-form-label">{{ 'schemas.field.allowedValues' | sqxTranslate }}</label> <label class="col-3 col-form-label">{{ 'schemas.field.allowedValues' | sqxTranslate }}</label>
<div class="col-6"> <div class="col-9">
<sqx-tag-editor formControlName="allowedValues"></sqx-tag-editor> <sqx-tag-editor formControlName="allowedValues"></sqx-tag-editor>
</div> </div>
</div> </div>

61
frontend/app/features/schemas/pages/schema/fields/types/string-validation.component.html

@ -13,20 +13,25 @@
<div class="form-group row"> <div class="form-group row">
<label class="col-3 col-form-label">{{ 'schemas.fieldTypes.string.length' | sqxTranslate }}</label> <label class="col-3 col-form-label">{{ 'schemas.fieldTypes.string.length' | sqxTranslate }}</label>
<div class="col-3 minmax-col"> <div class="col-9">
<div class="row no-gutters">
<div class="col">
<input type="number" class="form-control" formControlName="minLength" placeholder="{{ 'schemas.fieldTypes.string.lengthMin' | sqxTranslate }}"> <input type="number" class="form-control" formControlName="minLength" placeholder="{{ 'schemas.fieldTypes.string.lengthMin' | sqxTranslate }}">
<label class="col-form-label minmax-label">-</label>
</div> </div>
<div class="col-3"> <div class="col-auto">
<label class="col-form-label minmax">-</label>
</div>
<div class="col">
<input type="number" class="form-control" formControlName="maxLength" placeholder="{{ 'schemas.fieldTypes.string.lengthMax' | sqxTranslate }}"> <input type="number" class="form-control" formControlName="maxLength" placeholder="{{ 'schemas.fieldTypes.string.lengthMax' | sqxTranslate }}">
</div> </div>
</div> </div>
</div>
</div>
<div class="form-group row"> <div class="form-group row">
<label class="col-3 col-form-label" for="{{field.fieldId}}_fieldPattern">{{ 'common.pattern' | sqxTranslate }}</label> <label class="col-3 col-form-label" for="{{field.fieldId}}_fieldPattern">{{ 'common.pattern' | sqxTranslate }}</label>
<div class="col-6"> <div class="col-9">
<input type="text" class="form-control" id="{{field.fieldId}}_fieldPattern" formControlName="pattern" placeholder="{{ 'schemas.fieldTypes.string.pattern' | sqxTranslate }}" #inputPattern autocomplete="off" autocorrect="off" autocapitalize="off" (focus)="patternsModal.show()"> <input type="text" class="form-control" id="{{field.fieldId}}_fieldPattern" formControlName="pattern" placeholder="{{ 'schemas.fieldTypes.string.pattern' | sqxTranslate }}" #inputPattern autocomplete="off" autocorrect="off" autocapitalize="off" (focus)="patternsModal.show()">
<ng-container *ngIf="patterns.length > 0 && (showPatternSuggestions | async)"> <ng-container *ngIf="patterns.length > 0 && (showPatternSuggestions | async)">
@ -49,13 +54,13 @@
<div class="form-group row" *ngIf="showPatternMessage | async"> <div class="form-group row" *ngIf="showPatternMessage | async">
<label class="col-3 col-form-label" for="{{field.fieldId}}_fieldPatternMessage">{{ 'schemas.fieldTypes.string.patternMessage' | sqxTranslate }}</label> <label class="col-3 col-form-label" for="{{field.fieldId}}_fieldPatternMessage">{{ 'schemas.fieldTypes.string.patternMessage' | sqxTranslate }}</label>
<div class="col-6"> <div class="col-9">
<input type="text" class="form-control" id="{{field.fieldId}}_fieldPatternMessage" formControlName="patternMessage"> <input type="text" class="form-control" id="{{field.fieldId}}_fieldPatternMessage" formControlName="patternMessage">
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<div class="col-7 offset-3"> <div class="col-9 offset-3">
<sqx-form-hint> <sqx-form-hint>
{{ 'schemas.fieldTypes.string.wordHint' | sqxTranslate}} {{ 'schemas.fieldTypes.string.wordHint' | sqxTranslate}}
</sqx-form-hint> </sqx-form-hint>
@ -64,7 +69,7 @@
<div class="form-group row"> <div class="form-group row">
<label class="col-3 col-form-label">{{ 'schemas.fieldTypes.string.contentType' | sqxTranslate }}</label> <label class="col-3 col-form-label">{{ 'schemas.fieldTypes.string.contentType' | sqxTranslate }}</label>
<div class="col-6"> <div class="col-9">
<select class="custom-select" formControlName="contentType"> <select class="custom-select" formControlName="contentType">
<option *ngFor="let contentType of contentTypes" [ngValue]="contentType">{{contentType}}</option> <option *ngFor="let contentType of contentTypes" [ngValue]="contentType">{{contentType}}</option>
</select> </select>
@ -73,33 +78,55 @@
<div class="form-group row"> <div class="form-group row">
<label class="col-3 col-form-label">{{ 'schemas.fieldTypes.string.characters' | sqxTranslate }}</label> <label class="col-3 col-form-label">{{ 'schemas.fieldTypes.string.characters' | sqxTranslate }}</label>
<div class="col-3 minmax-col"> <div class="col-9">
<div class="row no-gutters">
<div class="col">
<input type="number" class="form-control" formControlName="minCharacters" placeholder="{{ 'schemas.fieldTypes.string.charactersMin' | sqxTranslate }}"> <input type="number" class="form-control" formControlName="minCharacters" placeholder="{{ 'schemas.fieldTypes.string.charactersMin' | sqxTranslate }}">
<label class="col-form-label minmax-label">-</label>
</div> </div>
<div class="col-3"> <div class="col-auto">
<label class="col-form-label minmax">-</label>
</div>
<div class="col">
<input type="number" class="form-control" formControlName="maxCharacters" placeholder="{{ 'schemas.fieldTypes.string.charactersMax' | sqxTranslate }}"> <input type="number" class="form-control" formControlName="maxCharacters" placeholder="{{ 'schemas.fieldTypes.string.charactersMax' | sqxTranslate }}">
</div> </div>
</div> </div>
</div>
</div>
<div class="form-group row"> <div class="form-group row">
<label class="col-3 col-form-label">{{ 'schemas.fieldTypes.string.words' | sqxTranslate }}</label> <label class="col-3 col-form-label">{{ 'schemas.fieldTypes.string.words' | sqxTranslate }}</label>
<div class="col-3 minmax-col"> <div class="col-9">
<div class="row no-gutters">
<div class="col">
<input type="number" class="form-control" formControlName="minWords" placeholder="{{ 'schemas.fieldTypes.string.wordsMin' | sqxTranslate }}"> <input type="number" class="form-control" formControlName="minWords" placeholder="{{ 'schemas.fieldTypes.string.wordsMin' | sqxTranslate }}">
<label class="col-form-label minmax-label">-</label>
</div> </div>
<div class="col-3"> <div class="col-auto">
<label class="col-form-label minmax">-</label>
</div>
<div class="col">
<input type="number" class="form-control" formControlName="maxWords" placeholder="{{ 'schemas.fieldTypes.string.wordsMax' | sqxTranslate }}"> <input type="number" class="form-control" formControlName="maxWords" placeholder="{{ 'schemas.fieldTypes.string.wordsMax' | sqxTranslate }}">
</div> </div>
</div> </div>
</div>
</div>
<div class="form-group row"> <div class="form-group row">
<label class="col-3 col-form-label" for="{{field.fieldId}}_fieldDefaultValue">{{ 'schemas.field.defaultValue' | sqxTranslate }}</label> <label class="col-3 col-form-label" for="{{field.fieldId}}_fieldDefaultValue">{{ 'schemas.field.defaultValue' | sqxTranslate }}</label>
<div class="col-6"> <div class="col-9">
<input type="text" class="form-control" id="{{field.fieldId}}_fieldDefaultValue" formControlName="defaultValue"> <input type="text" class="form-control" id="{{field.fieldId}}_fieldDefaultValue" formControlName="defaultValue">
</div> </div>
</div> </div>
<div class="form-group row" *ngIf="isLocalizable">
<label class="col-3 col-form-label" for="{{field.fieldId}}_fieldDefaultValues">{{ 'schemas.field.defaultValues' | sqxTranslate }}</label>
<div class="col-9">
<sqx-localized-input type="text" [languages]="languages" formControlName="defaultValues" id="{{field.fieldId}}_fieldDefaultValues"></sqx-localized-input>
<sqx-form-hint>
{{ 'schemas.field.defaultValuesHint' | sqxTranslate }}
</sqx-form-hint>
</div>
</div>
</div> </div>

10
frontend/app/features/schemas/pages/schema/fields/types/string-validation.component.scss

@ -1,11 +1,7 @@
.minmax { .minmax {
&-col { text-align: center;
position: relative; text-decoration: none;
} width: 2rem;
&-label {
@include absolute(0, -.2rem, auto, auto);
}
} }
.control-dropdown { .control-dropdown {

11
frontend/app/features/schemas/pages/schema/fields/types/string-validation.component.ts

@ -7,7 +7,7 @@
import { Component, Input, OnChanges, OnInit } from '@angular/core'; import { Component, Input, OnChanges, OnInit } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms'; import { FormControl, FormGroup } from '@angular/forms';
import { fadeAnimation, FieldDto, hasNoValue$, hasValue$, ModalModel, PatternDto, ResourceOwner, RootFieldDto, StringFieldPropertiesDto, STRING_CONTENT_TYPES, Types, value$ } from '@app/shared'; import { fadeAnimation, FieldDto, hasNoValue$, hasValue$, LanguageDto, ModalModel, PatternDto, ResourceOwner, RootFieldDto, StringFieldPropertiesDto, STRING_CONTENT_TYPES, Types, value$ } from '@app/shared';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
@Component({ @Component({
@ -31,6 +31,12 @@ export class StringValidationComponent extends ResourceOwner implements OnChange
@Input() @Input()
public patterns: ReadonlyArray<PatternDto>; public patterns: ReadonlyArray<PatternDto>;
@Input()
public languages: ReadonlyArray<LanguageDto>;
@Input()
public isLocalizable: boolean;
public contentTypes = STRING_CONTENT_TYPES; public contentTypes = STRING_CONTENT_TYPES;
public showPatternMessage: Observable<boolean>; public showPatternMessage: Observable<boolean>;
@ -79,6 +85,9 @@ export class StringValidationComponent extends ResourceOwner implements OnChange
this.fieldForm.setControl('defaultValue', this.fieldForm.setControl('defaultValue',
new FormControl(this.properties.defaultValue)); new FormControl(this.properties.defaultValue));
this.fieldForm.setControl('defaultValues',
new FormControl(this.properties.defaultValues));
this.showPatternSuggestions = this.showPatternSuggestions =
hasNoValue$(this.fieldForm.controls['pattern']); hasNoValue$(this.fieldForm.controls['pattern']);

4
frontend/app/features/schemas/pages/schema/fields/types/tags-ui.component.html

@ -2,7 +2,7 @@
<div class="form-group row"> <div class="form-group row">
<label class="col-3 col-form-label" for="{{field.fieldId}}_fieldPlaceholder">Placeholder</label> <label class="col-3 col-form-label" for="{{field.fieldId}}_fieldPlaceholder">Placeholder</label>
<div class="col-6"> <div class="col-9">
<input type="text" class="form-control" id="{{field.fieldId}}_fieldPlaceholder" maxlength="100" formControlName="placeholder"> <input type="text" class="form-control" id="{{field.fieldId}}_fieldPlaceholder" maxlength="100" formControlName="placeholder">
<sqx-form-hint> <sqx-form-hint>
@ -26,7 +26,7 @@
<div class="form-group row"> <div class="form-group row">
<label class="col-3 col-form-label">{{ 'schemas.field.allowedValues' | sqxTranslate }}</label> <label class="col-3 col-form-label">{{ 'schemas.field.allowedValues' | sqxTranslate }}</label>
<div class="col-6"> <div class="col-9">
<sqx-tag-editor formControlName="allowedValues"></sqx-tag-editor> <sqx-tag-editor formControlName="allowedValues"></sqx-tag-editor>
</div> </div>
</div> </div>

27
frontend/app/features/schemas/pages/schema/fields/types/tags-validation.component.html

@ -2,21 +2,38 @@
<div class="form-group row"> <div class="form-group row">
<label class="col-3 col-form-label">{{ 'schemas.fieldTypes.tags.count' | sqxTranslate }}</label> <label class="col-3 col-form-label">{{ 'schemas.fieldTypes.tags.count' | sqxTranslate }}</label>
<div class="col-3 minmax-col"> <div class="col-9">
<div class="row no-gutters">
<div class="col">
<input type="number" class="form-control" formControlName="minItems" placeholder="{{ 'schemas.fieldTypes.tags.countMin' | sqxTranslate }}"> <input type="number" class="form-control" formControlName="minItems" placeholder="{{ 'schemas.fieldTypes.tags.countMin' | sqxTranslate }}">
<label class="col-form-label minmax-label">-</label>
</div> </div>
<div class="col-3"> <div class="col-auto">
<label class="col-form-label minmax">-</label>
</div>
<div class="col">
<input type="number" class="form-control" formControlName="maxItems" placeholder="{{ 'schemas.fieldTypes.tags.countMax' | sqxTranslate }}"> <input type="number" class="form-control" formControlName="maxItems" placeholder="{{ 'schemas.fieldTypes.tags.countMax' | sqxTranslate }}">
</div> </div>
</div> </div>
</div>
</div>
<div class="form-group row"> <div class="form-group row">
<label class="col-3 col-form-label">{{ 'schemas.field.defaultValue' | sqxTranslate }}</label> <label class="col-3 col-form-label">{{ 'schemas.field.defaultValue' | sqxTranslate }}</label>
<div class="col-6"> <div class="col-9">
<sqx-tag-editor formControlName="defaultValue"></sqx-tag-editor> <sqx-tag-editor formControlName="defaultValue"></sqx-tag-editor>
</div> </div>
</div> </div>
<div class="form-group row" *ngIf="isLocalizable">
<label class="col-3 col-form-label">{{ 'schemas.field.defaultValues' | sqxTranslate }}</label>
<div class="col-9">
<sqx-localized-input type="tags" [languages]="languages" formControlName="defaultValues"></sqx-localized-input>
<sqx-form-hint>
{{ 'schemas.field.defaultValuesHint' | sqxTranslate }}
</sqx-form-hint>
</div>
</div>
</div> </div>

14
frontend/app/features/schemas/pages/schema/fields/types/tags-validation.component.scss

@ -1,13 +1,5 @@
.minmax { .minmax {
&-col { text-align: center;
position: relative; text-decoration: none;
} width: 2rem;
&-label {
@include absolute(0, -.2rem, auto, auto);
}
}
.form-group {
margin-top: .5rem;
} }

11
frontend/app/features/schemas/pages/schema/fields/types/tags-validation.component.ts

@ -7,7 +7,7 @@
import { Component, Input, OnInit } from '@angular/core'; import { Component, Input, OnInit } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms'; import { FormControl, FormGroup } from '@angular/forms';
import { FieldDto, TagsFieldPropertiesDto } from '@app/shared'; import { FieldDto, LanguageDto, TagsFieldPropertiesDto } from '@app/shared';
@Component({ @Component({
selector: 'sqx-tags-validation', selector: 'sqx-tags-validation',
@ -24,6 +24,12 @@ export class TagsValidationComponent implements OnInit {
@Input() @Input()
public properties: TagsFieldPropertiesDto; public properties: TagsFieldPropertiesDto;
@Input()
public languages: ReadonlyArray<LanguageDto>;
@Input()
public isLocalizable: boolean;
public ngOnInit() { public ngOnInit() {
this.fieldForm.setControl('maxItems', this.fieldForm.setControl('maxItems',
new FormControl(this.properties.maxItems)); new FormControl(this.properties.maxItems));
@ -33,5 +39,8 @@ export class TagsValidationComponent implements OnInit {
this.fieldForm.setControl('defaultValue', this.fieldForm.setControl('defaultValue',
new FormControl(this.properties.defaultValue)); new FormControl(this.properties.defaultValue));
this.fieldForm.setControl('defaultValues',
new FormControl(this.properties.defaultValues));
} }
} }

11
frontend/app/framework/angular/forms/editors/date-time-editor.component.ts

@ -5,7 +5,7 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/ */
import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, forwardRef, Input, OnInit, ViewChild } from '@angular/core'; import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, EventEmitter, forwardRef, Input, OnInit, Output, ViewChild } from '@angular/core';
import { FormControl, NG_VALUE_ACCESSOR } from '@angular/forms'; import { FormControl, NG_VALUE_ACCESSOR } from '@angular/forms';
import { DateHelper, DateTime, StatefulControlComponent, UIOptions } from '@app/framework/internal'; import { DateHelper, DateTime, StatefulControlComponent, UIOptions } from '@app/framework/internal';
import * as Pikaday from 'pikaday/pikaday'; import * as Pikaday from 'pikaday/pikaday';
@ -35,6 +35,9 @@ export class DateTimeEditorComponent extends StatefulControlComponent<{}, string
private dateTime: DateTime | null; private dateTime: DateTime | null;
private suppressEvents = false; private suppressEvents = false;
@Output()
public blur = new EventEmitter();
@Input() @Input()
public mode: 'DateTime' | 'Date'; public mode: 'DateTime' | 'Date';
@ -110,6 +113,12 @@ export class DateTimeEditorComponent extends StatefulControlComponent<{}, string
this.updateControls(); this.updateControls();
} }
public callTouched() {
this.blur.next();
super.callTouched();
}
public setDisabledState(isDisabled: boolean): void { public setDisabledState(isDisabled: boolean): void {
super.setDisabledState(isDisabled); super.setDisabledState(isDisabled);

56
frontend/app/framework/angular/forms/editors/localized-input.component.html

@ -0,0 +1,56 @@
<div class="row mb-1">
<div class="col">
<sqx-language-selector size="sm" [languages]="languages" [(selectedLanguage)]="selectedLanguage"></sqx-language-selector>
</div>
<div class="col-auto">
<button type="button" class="btn btn-sm btn-secondary" [disabled]="isEmpty" (click)="unset()">
<i class="icon-close"></i>
</button>
</div>
</div>
<ng-container [ngSwitch]="type">
<ng-container *ngSwitchCase="'text'">
<input class="form-control" [attr.type]="type" [attr.id]="id" [attr.name]="name"
[ngModel]="currentValue"
(ngModelChange)="setValue($event)"
(blur)="callTouched()" />
</ng-container>
<ng-container *ngSwitchCase="'number'">
<input class="form-control" [attr.type]="type" [attr.id]="id" [attr.name]="name"
[ngModel]="currentValue"
(ngModelChange)="setValue($event)"
(blur)="callTouched()" />
</ng-container>
<ng-container *ngSwitchCase="'tags'">
<sqx-tag-editor
[ngModel]="currentValue"
(ngModelChange)="setValue($event)"
(blur)="callTouched()">
</sqx-tag-editor>
</ng-container>
<ng-container *ngSwitchCase="'datetime'">
<sqx-date-time-editor mode="DateTime"
[ngModel]="currentValue"
(ngModelChange)="setValue($event)"
(blur)="callTouched()">
</sqx-date-time-editor>
</ng-container>
<ng-container *ngSwitchCase="'date'">
<sqx-date-time-editor mode="Date"
[ngModel]="currentValue"
(ngModelChange)="setValue($event)"
(blur)="callTouched()">
</sqx-date-time-editor>
</ng-container>
<ng-container *ngSwitchCase="'boolean'">
<div class="custom-control custom-checkbox">
<input class="custom-control-input" type="checkbox" [attr.id]="id"
[ngModel]="currentValue"
(ngModelChange)="setValue($event)"
(blur)="callTouched()">
<label class="custom-control-label" [attr.for]="id"></label>
</div>
</ng-container>
</ng-container>

0
frontend/app/framework/angular/forms/editors/localized-input.component.scss

100
frontend/app/framework/angular/forms/editors/localized-input.component.ts

@ -0,0 +1,100 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
// tslint:disable: whitespace
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, forwardRef, Input } from '@angular/core';
import { NG_VALUE_ACCESSOR } from '@angular/forms';
import { fadeAnimation, ModalModel, StatefulControlComponent, Types } from '@app/framework/internal';
import { Language } from './../../language-selector.component';
export const SQX_LOCALIZED_INPUT_CONTROL_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => LocalizedInputComponent), multi: true
};
const DEFAULT_LANGUAGE = { iso2Code: 'iv' };
@Component({
selector: 'sqx-localized-input',
styleUrls: ['./localized-input.component.scss'],
templateUrl: './localized-input.component.html',
providers: [
SQX_LOCALIZED_INPUT_CONTROL_VALUE_ACCESSOR
],
animations: [
fadeAnimation
],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class LocalizedInputComponent extends StatefulControlComponent<{}, { [key: string]: any }> {
private value: { [key: string]: any } | undefined;
@Input()
public languages: ReadonlyArray<Language>;
@Input()
public type: 'text' | 'boolean' | 'datetime' | 'date' | 'tags' = 'text';
@Input()
public name: string;
@Input()
public id: string;
public selectedLanguage: Language;
public dropdown = new ModalModel();
public get currentValue() {
if (!this.selectedLanguage || !this.value) {
return undefined;
}
return this.value[this.selectedLanguage.iso2Code];
}
public get isEmpty() {
if (!this.selectedLanguage || !this.value) {
return true;
}
return !this.value.hasOwnProperty(this.selectedLanguage.iso2Code);
}
constructor(changeDetector: ChangeDetectorRef) {
super(changeDetector, {
selectedLanguage: DEFAULT_LANGUAGE
});
}
public writeValue(obj: any) {
if (Types.isObject(obj)) {
this.value = obj;
} else {
this.value = {};
}
}
public setValue(value: any) {
this.value = { ...this.value || {} };
this.value[this.selectedLanguage.iso2Code] = value;
this.callChange(this.value);
}
public unset() {
this.value= { ...this.value || {} };
delete this.value[this.selectedLanguage.iso2Code];
if (Object.keys(this.value).length === 0) {
this.value = undefined;
}
this.callChange(this.value);
}
}

11
frontend/app/framework/angular/forms/editors/tag-editor.component.ts

@ -7,7 +7,7 @@
// tslint:disable: template-use-track-by-function // tslint:disable: template-use-track-by-function
import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, forwardRef, Input, OnChanges, OnInit, SimpleChanges, ViewChild } from '@angular/core'; import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, EventEmitter, forwardRef, Input, OnChanges, OnInit, Output, SimpleChanges, ViewChild } from '@angular/core';
import { FormControl, NG_VALUE_ACCESSOR } from '@angular/forms'; import { FormControl, NG_VALUE_ACCESSOR } from '@angular/forms';
import { fadeAnimation, getTagValues, Keys, ModalModel, StatefulControlComponent, StringConverter, TagValue, Types } from '@app/framework/internal'; import { fadeAnimation, getTagValues, Keys, ModalModel, StatefulControlComponent, StringConverter, TagValue, Types } from '@app/framework/internal';
import { distinctUntilChanged, map, tap } from 'rxjs/operators'; import { distinctUntilChanged, map, tap } from 'rxjs/operators';
@ -57,6 +57,9 @@ export class TagEditorComponent extends StatefulControlComponent<State, Readonly
@ViewChild('input', { static: false }) @ViewChild('input', { static: false })
public inputElement: ElementRef<HTMLInputElement>; public inputElement: ElementRef<HTMLInputElement>;
@Output()
public blur = new EventEmitter();
@Input() @Input()
public converter = StringConverter.INSTANCE; public converter = StringConverter.INSTANCE;
@ -377,6 +380,12 @@ export class TagEditorComponent extends StatefulControlComponent<State, Readonly
return this.snapshot.items.find(x => x.id === tagValue.id); return this.snapshot.items.find(x => x.id === tagValue.id);
} }
public callTouched() {
this.blur.next();
super.callTouched();
}
public onCut(event: ClipboardEvent) { public onCut(event: ClipboardEvent) {
if (!this.hasSelection()) { if (!this.hasSelection()) {
this.onCopy(event); this.onCopy(event);

0
frontend/app/shared/components/forms/language-selector.component.html → frontend/app/framework/angular/language-selector.component.html

0
frontend/app/shared/components/forms/language-selector.component.scss → frontend/app/framework/angular/language-selector.component.scss

8
frontend/app/shared/components/forms/language-selector.component.ts → frontend/app/framework/angular/language-selector.component.ts

@ -6,7 +6,7 @@
*/ */
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, OnInit, Output } from '@angular/core'; import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, OnInit, Output } from '@angular/core';
import { fadeAnimation, LanguageDto, ModalModel } from '@app/shared/internal'; import { fadeAnimation, ModalModel } from '@app/framework/internal';
export interface Language { iso2Code: string; englishName: string; isMasterLanguage: true; } export interface Language { iso2Code: string; englishName: string; isMasterLanguage: true; }
@ -24,7 +24,7 @@ export class LanguageSelectorComponent implements OnChanges, OnInit {
public selectedLanguageChange = new EventEmitter<Language>(); public selectedLanguageChange = new EventEmitter<Language>();
@Input() @Input()
public size: string; public size: 'sm' | 'md' | 'lg' = 'md';
@Input() @Input()
public languages: ReadonlyArray<Language> = []; public languages: ReadonlyArray<Language> = [];
@ -61,11 +61,13 @@ export class LanguageSelectorComponent implements OnChanges, OnInit {
} }
public selectLanguage(language: Language) { public selectLanguage(language: Language) {
if (language?.iso2Code !== this.selectedLanguage?.iso2Code) {
this.selectedLanguage = language; this.selectedLanguage = language;
this.selectedLanguageChange.emit(language); this.selectedLanguageChange.emit(language);
} }
}
public trackByLanguage(_index: number, language: LanguageDto) { public trackByLanguage(_index: number, language: Language) {
return language.iso2Code; return language.iso2Code;
} }
} }

2
frontend/app/framework/declarations.ts

@ -20,6 +20,7 @@ export * from './angular/forms/editors/date-time-editor.component';
export * from './angular/forms/editors/dropdown.component'; export * from './angular/forms/editors/dropdown.component';
export * from './angular/forms/editors/iframe-editor.component'; export * from './angular/forms/editors/iframe-editor.component';
export * from './angular/forms/editors/json-editor.component'; export * from './angular/forms/editors/json-editor.component';
export * from './angular/forms/editors/localized-input.component';
export * from './angular/forms/editors/stars.component'; export * from './angular/forms/editors/stars.component';
export * from './angular/forms/editors/tag-editor.component'; export * from './angular/forms/editors/tag-editor.component';
export * from './angular/forms/editors/toggle.component'; export * from './angular/forms/editors/toggle.component';
@ -40,6 +41,7 @@ export * from './angular/http/caching.interceptor';
export * from './angular/http/http-extensions'; export * from './angular/http/http-extensions';
export * from './angular/http/loading.interceptor'; export * from './angular/http/loading.interceptor';
export * from './angular/image-source.directive'; export * from './angular/image-source.directive';
export * from './angular/language-selector.component';
export * from './angular/list-view.component'; export * from './angular/list-view.component';
export * from './angular/modals/dialog-renderer.component'; export * from './angular/modals/dialog-renderer.component';
export * from './angular/modals/modal-dialog.component'; export * from './angular/modals/modal-dialog.component';

6
frontend/app/framework/module.ts

@ -12,7 +12,7 @@ import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { ModuleWithProviders, NgModule } from '@angular/core'; import { ModuleWithProviders, NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { ColorPickerModule } from 'ngx-color-picker'; import { ColorPickerModule } from 'ngx-color-picker';
import { AnalyticsService, AutocompleteComponent, AvatarComponent, CachingInterceptor, CanDeactivateGuard, CheckboxGroupComponent, ClipboardService, CodeComponent, CodeEditorComponent, ColorPickerComponent, ConfirmClickDirective, ControlErrorsComponent, CopyDirective, DarkenPipe, DatePipe, DateTimeEditorComponent, DayOfWeekPipe, DayPipe, DialogRendererComponent, DialogService, DisplayNamePipe, DropdownComponent, DurationPipe, EditableTitleComponent, ExternalLinkDirective, FileDropDirective, FileSizePipe, FocusOnInitDirective, FormAlertComponent, FormErrorComponent, FormHintComponent, FromNowPipe, FullDateTimePipe, HighlightPipe, HoverBackgroundDirective, IFrameEditorComponent, ImageSourceDirective, IndeterminateValueDirective, ISODatePipe, JsonEditorComponent, KeysPipe, KNumberPipe, LightenPipe, ListViewComponent, LoadingInterceptor, LoadingService, LocalStoreService, MarkdownInlinePipe, MarkdownPipe, MessageBus, ModalDialogComponent, ModalDirective, ModalPlacementDirective, MoneyPipe, MonthPipe, OnboardingService, OnboardingTooltipComponent, PagerComponent, PanelComponent, PanelContainerDirective, ParentLinkDirective, PopupLinkDirective, ProgressBarComponent, ResizedDirective, ResizeService, ResourceLoaderService, RootViewComponent, SafeHtmlPipe, SafeUrlPipe, ScrollActiveDirective, ShortcutComponent, ShortcutService, ShortDatePipe, ShortTimePipe, StarsComponent, StatusIconComponent, StopClickDirective, SyncScollingDirective, SyncWidthDirective, TabRouterlinkDirective, TagEditorComponent, TemplateWrapperDirective, TempService, TitleComponent, TitleService, ToggleComponent, TooltipDirective, TransformInputDirective, TranslatePipe } from './declarations'; import { AnalyticsService, AutocompleteComponent, AvatarComponent, CachingInterceptor, CanDeactivateGuard, CheckboxGroupComponent, ClipboardService, CodeComponent, CodeEditorComponent, ColorPickerComponent, ConfirmClickDirective, ControlErrorsComponent, CopyDirective, DarkenPipe, DatePipe, DateTimeEditorComponent, DayOfWeekPipe, DayPipe, DialogRendererComponent, DialogService, DisplayNamePipe, DropdownComponent, DurationPipe, EditableTitleComponent, ExternalLinkDirective, FileDropDirective, FileSizePipe, FocusOnInitDirective, FormAlertComponent, FormErrorComponent, FormHintComponent, FromNowPipe, FullDateTimePipe, HighlightPipe, HoverBackgroundDirective, IFrameEditorComponent, ImageSourceDirective, IndeterminateValueDirective, ISODatePipe, JsonEditorComponent, KeysPipe, KNumberPipe, LanguageSelectorComponent, LightenPipe, ListViewComponent, LoadingInterceptor, LoadingService, LocalizedInputComponent, LocalStoreService, MarkdownInlinePipe, MarkdownPipe, MessageBus, ModalDialogComponent, ModalDirective, ModalPlacementDirective, MoneyPipe, MonthPipe, OnboardingService, OnboardingTooltipComponent, PagerComponent, PanelComponent, PanelContainerDirective, ParentLinkDirective, PopupLinkDirective, ProgressBarComponent, ResizedDirective, ResizeService, ResourceLoaderService, RootViewComponent, SafeHtmlPipe, SafeUrlPipe, ScrollActiveDirective, ShortcutComponent, ShortcutService, ShortDatePipe, ShortTimePipe, StarsComponent, StatusIconComponent, StopClickDirective, SyncScollingDirective, SyncWidthDirective, TabRouterlinkDirective, TagEditorComponent, TemplateWrapperDirective, TempService, TitleComponent, TitleService, ToggleComponent, TooltipDirective, TransformInputDirective, TranslatePipe } from './declarations';
@NgModule({ @NgModule({
imports: [ imports: [
@ -59,8 +59,10 @@ import { AnalyticsService, AutocompleteComponent, AvatarComponent, CachingInterc
JsonEditorComponent, JsonEditorComponent,
KeysPipe, KeysPipe,
KNumberPipe, KNumberPipe,
LanguageSelectorComponent,
LightenPipe, LightenPipe,
ListViewComponent, ListViewComponent,
LocalizedInputComponent,
MarkdownInlinePipe, MarkdownInlinePipe,
MarkdownPipe, MarkdownPipe,
ModalDialogComponent, ModalDialogComponent,
@ -137,8 +139,10 @@ import { AnalyticsService, AutocompleteComponent, AvatarComponent, CachingInterc
JsonEditorComponent, JsonEditorComponent,
KeysPipe, KeysPipe,
KNumberPipe, KNumberPipe,
LanguageSelectorComponent,
LightenPipe, LightenPipe,
ListViewComponent, ListViewComponent,
LocalizedInputComponent,
MarkdownInlinePipe, MarkdownInlinePipe,
MarkdownPipe, MarkdownPipe,
ModalDialogComponent, ModalDialogComponent,

2
frontend/app/shared/components/notifo.component.scss

@ -1,4 +1,5 @@
:host ::ng-deep { :host ::ng-deep {
.notifo {
.notifo-notifications-button { .notifo-notifications-button {
margin-left: .75rem; margin-left: .75rem;
margin-right: .75rem; margin-right: .75rem;
@ -24,3 +25,4 @@
min-width: 3rem; min-width: 3rem;
} }
} }
}

1
frontend/app/shared/declarations.ts

@ -21,7 +21,6 @@ export * from './components/assets/pipes';
export * from './components/comments/comment.component'; export * from './components/comments/comment.component';
export * from './components/comments/comments.component'; export * from './components/comments/comments.component';
export * from './components/forms/geolocation-editor.component'; export * from './components/forms/geolocation-editor.component';
export * from './components/forms/language-selector.component';
export * from './components/forms/markdown-editor.component'; export * from './components/forms/markdown-editor.component';
export * from './components/forms/references-checkboxes.component'; export * from './components/forms/references-checkboxes.component';
export * from './components/forms/references-dropdown.component'; export * from './components/forms/references-dropdown.component';

4
frontend/app/shared/module.ts

@ -13,7 +13,7 @@ import { ModuleWithProviders, NgModule } from '@angular/core';
import { RouterModule } from '@angular/router'; import { RouterModule } from '@angular/router';
import { SqxFrameworkModule } from '@app/framework'; import { SqxFrameworkModule } from '@app/framework';
import { MentionModule } from 'angular-mentions'; import { MentionModule } from 'angular-mentions';
import { AppFormComponent, AppLanguagesService, AppMustExistGuard, AppsService, AppsState, AssetComponent, AssetDialogComponent, AssetFolderComponent, AssetFolderDialogComponent, AssetHistoryComponent, AssetPathComponent, AssetPreviewUrlPipe, AssetsDialogState, AssetsListComponent, AssetsSelectorComponent, AssetsService, AssetsState, AssetUploaderComponent, AssetUploaderState, AssetUrlPipe, AuthInterceptor, AuthService, AutoSaveService, BackupsService, BackupsState, ClientsService, ClientsState, CommentComponent, CommentsComponent, CommentsService, ContentMustExistGuard, ContentsService, ContentsState, ContributorsService, ContributorsState, FileIconPipe, FilterComparisonComponent, FilterLogicalComponent, FilterNodeComponent, GeolocationEditorComponent, GraphQlService, HelpComponent, HelpMarkdownPipe, HelpService, HistoryComponent, HistoryListComponent, HistoryMessagePipe, HistoryService, ImageCropperComponent, ImageFocusPointComponent, LanguageSelectorComponent, LanguagesService, LanguagesState, LoadAppsGuard, LoadLanguagesGuard, MarkdownEditorComponent, MustBeAuthenticatedGuard, MustBeNotAuthenticatedGuard, NewsService, NotifoComponent, PatternsService, PatternsState, PlansService, PlansState, QueryComponent, QueryListComponent, QueryPathComponent, ReferencesCheckboxesComponent, ReferencesDropdownComponent, ReferencesTagsComponent, RichEditorComponent, RolesService, RolesState, RuleEventsState, RulesService, RulesState, SavedQueriesComponent, SchemaCategoryComponent, SchemaMustExistGuard, SchemaMustExistPublishedGuard, SchemaMustNotBeSingletonGuard, SchemasService, SchemasState, SchemaTagSource, SearchFormComponent, SortingComponent, StockPhotoService, TableHeaderComponent, TranslationsService, UIService, UIState, UnsetAppGuard, UnsetContentGuard, UsagesService, UserDtoPicture, UserIdPicturePipe, UserNamePipe, UserNameRefPipe, UserPicturePipe, UserPictureRefPipe, UsersProviderService, UsersService, WorkflowsService, WorkflowsState } from './declarations'; import { AppFormComponent, AppLanguagesService, AppMustExistGuard, AppsService, AppsState, AssetComponent, AssetDialogComponent, AssetFolderComponent, AssetFolderDialogComponent, AssetHistoryComponent, AssetPathComponent, AssetPreviewUrlPipe, AssetsDialogState, AssetsListComponent, AssetsSelectorComponent, AssetsService, AssetsState, AssetUploaderComponent, AssetUploaderState, AssetUrlPipe, AuthInterceptor, AuthService, AutoSaveService, BackupsService, BackupsState, ClientsService, ClientsState, CommentComponent, CommentsComponent, CommentsService, ContentMustExistGuard, ContentsService, ContentsState, ContributorsService, ContributorsState, FileIconPipe, FilterComparisonComponent, FilterLogicalComponent, FilterNodeComponent, GeolocationEditorComponent, GraphQlService, HelpComponent, HelpMarkdownPipe, HelpService, HistoryComponent, HistoryListComponent, HistoryMessagePipe, HistoryService, ImageCropperComponent, ImageFocusPointComponent, LanguagesService, LanguagesState, LoadAppsGuard, LoadLanguagesGuard, MarkdownEditorComponent, MustBeAuthenticatedGuard, MustBeNotAuthenticatedGuard, NewsService, NotifoComponent, PatternsService, PatternsState, PlansService, PlansState, QueryComponent, QueryListComponent, QueryPathComponent, ReferencesCheckboxesComponent, ReferencesDropdownComponent, ReferencesTagsComponent, RichEditorComponent, RolesService, RolesState, RuleEventsState, RulesService, RulesState, SavedQueriesComponent, SchemaCategoryComponent, SchemaMustExistGuard, SchemaMustExistPublishedGuard, SchemaMustNotBeSingletonGuard, SchemasService, SchemasState, SchemaTagSource, SearchFormComponent, SortingComponent, StockPhotoService, TableHeaderComponent, TranslationsService, UIService, UIState, UnsetAppGuard, UnsetContentGuard, UsagesService, UserDtoPicture, UserIdPicturePipe, UserNamePipe, UserNameRefPipe, UserPicturePipe, UserPictureRefPipe, UsersProviderService, UsersService, WorkflowsService, WorkflowsState } from './declarations';
import { SearchService } from './services/search.service'; import { SearchService } from './services/search.service';
@NgModule({ @NgModule({
@ -50,7 +50,6 @@ import { SearchService } from './services/search.service';
HistoryMessagePipe, HistoryMessagePipe,
ImageCropperComponent, ImageCropperComponent,
ImageFocusPointComponent, ImageFocusPointComponent,
LanguageSelectorComponent,
MarkdownEditorComponent, MarkdownEditorComponent,
NotifoComponent, NotifoComponent,
QueryComponent, QueryComponent,
@ -94,7 +93,6 @@ import { SearchService } from './services/search.service';
HistoryComponent, HistoryComponent,
HistoryListComponent, HistoryListComponent,
HistoryMessagePipe, HistoryMessagePipe,
LanguageSelectorComponent,
MarkdownEditorComponent, MarkdownEditorComponent,
NotifoComponent, NotifoComponent,
QueryListComponent, QueryListComponent,

9
frontend/app/shared/services/schemas.types.ts

@ -129,6 +129,8 @@ export interface FieldPropertiesVisitor<T> {
visitUI(properties: UIFieldPropertiesDto): T; visitUI(properties: UIFieldPropertiesDto): T;
} }
type DefaultValue<T> = { [key: string]: T | null | undefined };
export abstract class FieldPropertiesDto { export abstract class FieldPropertiesDto {
public abstract fieldType: FieldType; public abstract fieldType: FieldType;
@ -180,6 +182,7 @@ export class AssetsFieldPropertiesDto extends FieldPropertiesDto {
public readonly previewMode: AssetPreviewMode; public readonly previewMode: AssetPreviewMode;
public readonly defaultValue?: ReadonlyArray<string>; public readonly defaultValue?: ReadonlyArray<string>;
public readonly defaultValues?: DefaultValue<ReadonlyArray<string>>;
public readonly allowDuplicates?: boolean; public readonly allowDuplicates?: boolean;
public readonly allowedExtensions?: ReadonlyArray<string>; public readonly allowedExtensions?: ReadonlyArray<string>;
public readonly resolveFirst: boolean; public readonly resolveFirst: boolean;
@ -215,6 +218,7 @@ export class BooleanFieldPropertiesDto extends FieldPropertiesDto {
public readonly fieldType = 'Boolean'; public readonly fieldType = 'Boolean';
public readonly defaultValue?: boolean; public readonly defaultValue?: boolean;
public readonly defaultValues?: DefaultValue<boolean>;
public readonly editor: BooleanFieldEditor = 'Checkbox'; public readonly editor: BooleanFieldEditor = 'Checkbox';
public readonly inlineEditable: boolean = false; public readonly inlineEditable: boolean = false;
@ -239,6 +243,7 @@ export class DateTimeFieldPropertiesDto extends FieldPropertiesDto {
public readonly calculatedDefaultValue?: string; public readonly calculatedDefaultValue?: string;
public readonly defaultValue?: string; public readonly defaultValue?: string;
public readonly defaultValues?: DefaultValue<string>;
public readonly editor: DateTimeFieldEditor = 'DateTime'; public readonly editor: DateTimeFieldEditor = 'DateTime';
public readonly maxValue?: string; public readonly maxValue?: string;
public readonly minValue?: string; public readonly minValue?: string;
@ -298,6 +303,7 @@ export class NumberFieldPropertiesDto extends FieldPropertiesDto {
public readonly allowedValues?: ReadonlyArray<number>; public readonly allowedValues?: ReadonlyArray<number>;
public readonly defaultValue?: number; public readonly defaultValue?: number;
public readonly defaultValues?: DefaultValue<number>;
public readonly editor: NumberFieldEditor = 'Input'; public readonly editor: NumberFieldEditor = 'Input';
public readonly inlineEditable: boolean = false; public readonly inlineEditable: boolean = false;
public readonly isUnique: boolean = false; public readonly isUnique: boolean = false;
@ -327,6 +333,7 @@ export class ReferencesFieldPropertiesDto extends FieldPropertiesDto {
public readonly allowDuplicates?: boolean; public readonly allowDuplicates?: boolean;
public readonly defaultValue?: ReadonlyArray<string>; public readonly defaultValue?: ReadonlyArray<string>;
public readonly defaultValues?: DefaultValue<ReadonlyArray<string>>;
public readonly editor: ReferencesFieldEditor = 'List'; public readonly editor: ReferencesFieldEditor = 'List';
public readonly maxItems?: number; public readonly maxItems?: number;
public readonly minItems?: number; public readonly minItems?: number;
@ -374,6 +381,7 @@ export class StringFieldPropertiesDto extends FieldPropertiesDto {
public readonly allowedValues?: ReadonlyArray<string>; public readonly allowedValues?: ReadonlyArray<string>;
public readonly defaultValue?: string; public readonly defaultValue?: string;
public readonly defaultValues?: DefaultValue<string>;
public readonly editor: StringFieldEditor = 'Input'; public readonly editor: StringFieldEditor = 'Input';
public readonly inlineEditable: boolean = false; public readonly inlineEditable: boolean = false;
public readonly isUnique: boolean = false; public readonly isUnique: boolean = false;
@ -409,6 +417,7 @@ export class TagsFieldPropertiesDto extends FieldPropertiesDto {
public readonly allowedValues?: ReadonlyArray<string>; public readonly allowedValues?: ReadonlyArray<string>;
public readonly defaultValue?: ReadonlyArray<string>; public readonly defaultValue?: ReadonlyArray<string>;
public readonly defaultValues?: DefaultValue<ReadonlyArray<string>>;
public readonly editor: TagsFieldEditor = 'Tags'; public readonly editor: TagsFieldEditor = 'Tags';
public readonly maxItems?: number; public readonly maxItems?: number;
public readonly minItems?: number; public readonly minItems?: number;

66
frontend/app/shared/state/contents.forms.spec.ts

@ -152,7 +152,7 @@ describe('ArrayField', () => {
}); });
it('should return default value as null', () => { it('should return default value as null', () => {
expect(FieldDefaultValue.get(field)).toBeNull(); expect(FieldDefaultValue.get(field, 'iv')).toBeNull();
}); });
}); });
@ -182,7 +182,13 @@ describe('AssetsField', () => {
it('should return default value from properties', () => { it('should return default value from properties', () => {
const field2 = createField({ properties: createProperties('Assets', { defaultValue: ['1', '2'] }) }); const field2 = createField({ properties: createProperties('Assets', { defaultValue: ['1', '2'] }) });
expect(FieldDefaultValue.get(field2)).toEqual(['1', '2']); expect(FieldDefaultValue.get(field2, 'iv')).toEqual(['1', '2']);
});
it('should override default value from localizable properties', () => {
const field2 = createField({ properties: createProperties('Assets', { defaultValue: ['1', '2'], defaultValues: { 'iv': null } }) });
expect(FieldDefaultValue.get(field2, 'iv')).toBeNull();
}); });
}); });
@ -208,7 +214,13 @@ describe('TagsField', () => {
it('should return default value from properties', () => { it('should return default value from properties', () => {
const field2 = createField({ properties: createProperties('Tags', { defaultValue: ['1', '2'] }) }); const field2 = createField({ properties: createProperties('Tags', { defaultValue: ['1', '2'] }) });
expect(FieldDefaultValue.get(field2)).toEqual(['1', '2']); expect(FieldDefaultValue.get(field2, 'iv')).toEqual(['1', '2']);
});
it('should override default value from localizable properties', () => {
const field2 = createField({ properties: createProperties('Tags', { defaultValue: ['1', '2'], defaultValues: { 'iv': null } }) });
expect(FieldDefaultValue.get(field2, 'iv')).toBeNull();
}); });
}); });
@ -234,7 +246,13 @@ describe('BooleanField', () => {
it('should return default value from properties', () => { it('should return default value from properties', () => {
const field2 = createField({ properties: createProperties('Boolean', { editor: 'Checkbox', defaultValue: true }) }); const field2 = createField({ properties: createProperties('Boolean', { editor: 'Checkbox', defaultValue: true }) });
expect(FieldDefaultValue.get(field2)).toBeTruthy(); expect(FieldDefaultValue.get(field2, 'iv')).toBeTruthy();
});
it('should override default value from localizable properties', () => {
const field2 = createField({ properties: createProperties('Boolean', { defaultValue: true, defaultValues: { 'iv': null } }) });
expect(FieldDefaultValue.get(field2, 'iv')).toBeNull();
}); });
}); });
@ -284,19 +302,25 @@ describe('DateTimeField', () => {
it('should return default from properties value', () => { it('should return default from properties value', () => {
const field2 = createField({ properties: createProperties('DateTime', { editor: 'DateTime', defaultValue: '2017-10-12T16:00:00Z' }) }); const field2 = createField({ properties: createProperties('DateTime', { editor: 'DateTime', defaultValue: '2017-10-12T16:00:00Z' }) });
expect(FieldDefaultValue.get(field2)).toEqual('2017-10-12T16:00:00Z'); expect(FieldDefaultValue.get(field2, 'iv')).toEqual('2017-10-12T16:00:00Z');
});
it('should override default value from localizable properties', () => {
const field2 = createField({ properties: createProperties('DateTime', { defaultValue: '2017-10-12T16:00:00Z', defaultValues: { 'iv': null } }) });
expect(FieldDefaultValue.get(field2, 'iv')).toBeNull();
}); });
it('should return default from Today', () => { it('should return default from Today', () => {
const field2 = createField({ properties: createProperties('DateTime', { editor: 'DateTime', calculatedDefaultValue: 'Today' }) }); const field2 = createField({ properties: createProperties('DateTime', { editor: 'DateTime', calculatedDefaultValue: 'Today' }) });
expect(FieldDefaultValue.get(field2, now)).toEqual('2017-10-12T00:00:00Z'); expect(FieldDefaultValue.get(field2, 'iv', now)).toEqual('2017-10-12T00:00:00Z');
}); });
it('should return default value from Today', () => { it('should return default value from Today', () => {
const field2 = createField({ properties: createProperties('DateTime', { editor: 'DateTime', calculatedDefaultValue: 'Now' }) }); const field2 = createField({ properties: createProperties('DateTime', { editor: 'DateTime', calculatedDefaultValue: 'Now' }) });
expect(FieldDefaultValue.get(field2, now)).toEqual('2017-10-12T16:30:10Z'); expect(FieldDefaultValue.get(field2, 'iv', now)).toEqual('2017-10-12T16:30:10Z');
}); });
}); });
@ -316,7 +340,7 @@ describe('GeolocationField', () => {
}); });
it('should return default value as null', () => { it('should return default value as null', () => {
expect(FieldDefaultValue.get(field)).toBeNull(); expect(FieldDefaultValue.get(field, 'iv')).toBeNull();
}); });
}); });
@ -336,7 +360,7 @@ describe('JsonField', () => {
}); });
it('should return default value as null', () => { it('should return default value as null', () => {
expect(FieldDefaultValue.get(field)).toBeNull(); expect(FieldDefaultValue.get(field, 'iv')).toBeNull();
}); });
}); });
@ -388,7 +412,13 @@ describe('NumberField', () => {
it('should return default value from properties', () => { it('should return default value from properties', () => {
const field2 = createField({ properties: createProperties('Number', { defaultValue: 13 }) }); const field2 = createField({ properties: createProperties('Number', { defaultValue: 13 }) });
expect(FieldDefaultValue.get(field2)).toEqual(13); expect(FieldDefaultValue.get(field2, 'iv')).toEqual(13);
});
it('should override default value from localizable properties', () => {
const field2 = createField({ properties: createProperties('Number', { defaultValue: 13, defaultValues: { 'iv': null } }) });
expect(FieldDefaultValue.get(field2, 'iv')).toBeNull();
}); });
}); });
@ -418,7 +448,13 @@ describe('ReferencesField', () => {
it('should return default value from properties', () => { it('should return default value from properties', () => {
const field2 = createField({ properties: createProperties('References', { defaultValue: ['1', '2'] }) }); const field2 = createField({ properties: createProperties('References', { defaultValue: ['1', '2'] }) });
expect(FieldDefaultValue.get(field2)).toEqual(['1', '2']); expect(FieldDefaultValue.get(field2, 'iv')).toEqual(['1', '2']);
});
it('should override default value from localizable properties', () => {
const field2 = createField({ properties: createProperties('References', { defaultValue: ['1', '2'], defaultValues: { 'iv': null } }) });
expect(FieldDefaultValue.get(field2, 'iv')).toBeNull();
}); });
}); });
@ -458,7 +494,13 @@ describe('StringField', () => {
it('should return default value from properties', () => { it('should return default value from properties', () => {
const field2 = createField({ properties: createProperties('String', { defaultValue: 'MyDefault' }) }); const field2 = createField({ properties: createProperties('String', { defaultValue: 'MyDefault' }) });
expect(FieldDefaultValue.get(field2)).toEqual('MyDefault'); expect(FieldDefaultValue.get(field2, 'iv')).toEqual('MyDefault');
});
it('should override default value from localizable properties', () => {
const field2 = createField({ properties: createProperties('String', { defaultValue: 'MyDefault', defaultValues: { 'iv': null } }) });
expect(FieldDefaultValue.get(field2, 'iv')).toBeNull();
}); });
}); });

29
frontend/app/shared/state/contents.forms.ts

@ -219,8 +219,8 @@ export class FieldForm extends AbstractContentForm<RootFieldDto, FormGroup> {
for (const { key, isOptional } of partitions.getAll(field)) { for (const { key, isOptional } of partitions.getAll(field)) {
const child = const child =
field.isArray ? field.isArray ?
new FieldArrayForm(field, isOptional, rules, this.remoteValidator) : new FieldArrayForm(field, isOptional, key, rules, this.remoteValidator) :
new FieldValueForm(field, isOptional, this.remoteValidator); new FieldValueForm(field, isOptional, key, this.remoteValidator);
this.partitions[key] = child; this.partitions[key] = child;
@ -295,14 +295,14 @@ export class FieldForm extends AbstractContentForm<RootFieldDto, FormGroup> {
} }
export class FieldValueForm extends AbstractContentForm<RootFieldDto, FormControl> { export class FieldValueForm extends AbstractContentForm<RootFieldDto, FormControl> {
constructor(field: RootFieldDto, isOptional: boolean, constructor(field: RootFieldDto, isOptional: boolean, key: string,
remoteValidator?: ValidatorFn remoteValidator?: ValidatorFn
) { ) {
super(field, FieldValueForm.buildControl(field, isOptional, remoteValidator), isOptional); super(field, FieldValueForm.buildControl(field, isOptional, key, remoteValidator), isOptional);
} }
private static buildControl(field: RootFieldDto, isOptional: boolean, remoteValidator?: ValidatorFn) { private static buildControl(field: RootFieldDto, isOptional: boolean, key: string, remoteValidator?: ValidatorFn) {
const value = FieldDefaultValue.get(field); const value = FieldDefaultValue.get(field, key);
const validators = FieldsValidators.create(field, isOptional); const validators = FieldsValidators.create(field, isOptional);
@ -330,6 +330,7 @@ export class FieldArrayForm extends AbstractContentForm<RootFieldDto, FormArray>
} }
constructor(field: RootFieldDto, isOptional: boolean, constructor(field: RootFieldDto, isOptional: boolean,
private readonly partition: string,
private readonly allRules: CompiledRule[], private readonly allRules: CompiledRule[],
private readonly remoteValidator?: ValidatorFn private readonly remoteValidator?: ValidatorFn
) { ) {
@ -341,7 +342,7 @@ export class FieldArrayForm extends AbstractContentForm<RootFieldDto, FormArray>
} }
public addItem(source?: FieldArrayItemForm) { public addItem(source?: FieldArrayItemForm) {
const child = new FieldArrayItemForm(this.field, this.isOptional, this.allRules, source, this.remoteValidator); const child = new FieldArrayItemForm(this.field, this.isOptional, this.allRules, source, this.partition, this.remoteValidator);
this.items = [...this.items, child]; this.items = [...this.items, child];
@ -401,7 +402,7 @@ export class FieldArrayItemForm extends AbstractContentForm<RootFieldDto, FormGr
public readonly sections: ReadonlyArray<FieldSection<NestedFieldDto, FieldArrayItemValueForm>>; public readonly sections: ReadonlyArray<FieldSection<NestedFieldDto, FieldArrayItemValueForm>>;
constructor(field: RootFieldDto, isOptional: boolean, allRules: CompiledRule[], source: FieldArrayItemForm | undefined, constructor(field: RootFieldDto, isOptional: boolean, allRules: CompiledRule[], source: FieldArrayItemForm | undefined, partition: string,
private readonly remoteValidator?: ValidatorFn private readonly remoteValidator?: ValidatorFn
) { ) {
super(field, new FormGroup({}), isOptional); super(field, new FormGroup({}), isOptional);
@ -413,7 +414,7 @@ export class FieldArrayItemForm extends AbstractContentForm<RootFieldDto, FormGr
for (const nestedField of field.nested) { for (const nestedField of field.nested) {
if (nestedField.properties.isContentField) { if (nestedField.properties.isContentField) {
const child = new FieldArrayItemValueForm(nestedField, field, allRules, isOptional, source, this.remoteValidator); const child = new FieldArrayItemValueForm(nestedField, field, allRules, isOptional, partition, source, this.remoteValidator);
currentFields.push(child); currentFields.push(child);
@ -455,11 +456,11 @@ export class FieldArrayItemForm extends AbstractContentForm<RootFieldDto, FormGr
export class FieldArrayItemValueForm extends AbstractContentForm<NestedFieldDto, FormControl> { export class FieldArrayItemValueForm extends AbstractContentForm<NestedFieldDto, FormControl> {
private isRequired = false; private isRequired = false;
constructor(field: NestedFieldDto, parent: RootFieldDto, rules: CompiledRule[], isOptional: boolean, source: FieldArrayItemForm | undefined, constructor(field: NestedFieldDto, parent: RootFieldDto, rules: CompiledRule[], isOptional: boolean, partition: string,
remoteValidator?: ValidatorFn source: FieldArrayItemForm | undefined, remoteValidator?: ValidatorFn
) { ) {
super(field, super(field,
FieldArrayItemValueForm.buildControl(field, isOptional, remoteValidator, source), FieldArrayItemValueForm.buildControl(field, isOptional, partition, remoteValidator, source),
isOptional, isOptional,
FieldArrayItemValueForm.buildRules(field, parent, rules) FieldArrayItemValueForm.buildRules(field, parent, rules)
); );
@ -492,8 +493,8 @@ export class FieldArrayItemValueForm extends AbstractContentForm<NestedFieldDto,
return rules.filter(x => x.field === fullName); return rules.filter(x => x.field === fullName);
} }
private static buildControl(field: NestedFieldDto, isOptional: boolean, remoteValidator?: ValidatorFn, source?: FieldArrayItemForm) { private static buildControl(field: NestedFieldDto, isOptional: boolean, partition: string, remoteValidator?: ValidatorFn, source?: FieldArrayItemForm) {
let value = FieldDefaultValue.get(field); let value = FieldDefaultValue.get(field, partition);
if (source) { if (source) {
const sourceField = source.form.get(field.name); const sourceField = source.form.get(field.name);

27
frontend/app/shared/state/contents.forms.visitors.ts

@ -344,12 +344,13 @@ export class FieldsValidators implements FieldPropertiesVisitor<ReadonlyArray<Va
export class FieldDefaultValue implements FieldPropertiesVisitor<any> { export class FieldDefaultValue implements FieldPropertiesVisitor<any> {
private constructor( private constructor(
private readonly partitionKey: string,
private readonly now?: DateTime private readonly now?: DateTime
) { ) {
} }
public static get(field: FieldDto, now?: DateTime) { public static get(field: FieldDto, partitionKey: string, now?: DateTime) {
return field.properties.accept(new FieldDefaultValue(now)); return field.properties.accept(new FieldDefaultValue(partitionKey, now));
} }
public visitDateTime(properties: DateTimeFieldPropertiesDto): any { public visitDateTime(properties: DateTimeFieldPropertiesDto): any {
@ -360,7 +361,7 @@ export class FieldDefaultValue implements FieldPropertiesVisitor<any> {
} else if (properties.calculatedDefaultValue === 'Today') { } else if (properties.calculatedDefaultValue === 'Today') {
return `${now.toISODate()}T00:00:00Z`; return `${now.toISODate()}T00:00:00Z`;
} else { } else {
return properties.defaultValue; return this.getValue(properties.defaultValue, properties.defaultValues);
} }
} }
@ -369,11 +370,11 @@ export class FieldDefaultValue implements FieldPropertiesVisitor<any> {
} }
public visitAssets(properties: AssetsFieldPropertiesDto): any { public visitAssets(properties: AssetsFieldPropertiesDto): any {
return properties.defaultValue; return this.getValue(properties.defaultValue, properties.defaultValues);
} }
public visitBoolean(properties: BooleanFieldPropertiesDto): any { public visitBoolean(properties: BooleanFieldPropertiesDto): any {
return properties.defaultValue; return this.getValue(properties.defaultValue, properties.defaultValues);
} }
public visitGeolocation(_: GeolocationFieldPropertiesDto): any { public visitGeolocation(_: GeolocationFieldPropertiesDto): any {
@ -385,22 +386,30 @@ export class FieldDefaultValue implements FieldPropertiesVisitor<any> {
} }
public visitNumber(properties: NumberFieldPropertiesDto): any { public visitNumber(properties: NumberFieldPropertiesDto): any {
return properties.defaultValue; return this.getValue(properties.defaultValue, properties.defaultValues);
} }
public visitReferences(properties: ReferencesFieldPropertiesDto): any { public visitReferences(properties: ReferencesFieldPropertiesDto): any {
return properties.defaultValue; return this.getValue(properties.defaultValue, properties.defaultValues);
} }
public visitString(properties: StringFieldPropertiesDto): any { public visitString(properties: StringFieldPropertiesDto): any {
return properties.defaultValue; return this.getValue(properties.defaultValue, properties.defaultValues);
} }
public visitTags(properties: TagsFieldPropertiesDto): any { public visitTags(properties: TagsFieldPropertiesDto): any {
return properties.defaultValue; return this.getValue(properties.defaultValue, properties.defaultValues);
} }
public visitUI(_: UIFieldPropertiesDto): any { public visitUI(_: UIFieldPropertiesDto): any {
return null; return null;
} }
private getValue(value: any, values?: object) {
if (values && values.hasOwnProperty(this.partitionKey)) {
return values[this.partitionKey];
}
return value;
}
} }

3
frontend/app/shared/state/languages.state.ts

@ -58,6 +58,9 @@ export class LanguagesState extends State<Snapshot> {
public languages = public languages =
this.project(x => x.languages); this.project(x => x.languages);
public languagesDtos =
this.project(x => x.languages.map(y => y.language));
public newLanguages = public newLanguages =
this.project(x => x.allLanguagesNew); this.project(x => x.allLanguagesNew);

Loading…
Cancel
Save