Browse Source

Autocompletion fixes (#972)

* Several fixes.

* Improved autocompletion.

* Fix tests.
pull/973/head
Sebastian Stehle 4 years ago
committed by GitHub
parent
commit
fd51ab9f63
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      backend/extensions/Squidex.Extensions/Actions/Webhook/WebhookAction.cs
  2. 1784
      backend/src/Squidex.Domain.Apps.Core.Model/FieldDescriptions.Designer.cs
  3. 11
      backend/src/Squidex.Domain.Apps.Core.Model/FieldDescriptions.resx
  4. 2
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentDataObject.cs
  5. 2
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/IScriptDescriptor.cs
  6. 2
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptExecutionContext.cs
  7. 62
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptingCompleter.cs
  8. 5
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptingValue.cs
  9. 17
      backend/src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs
  10. 21
      backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/ScriptingCompleterTests.cs
  11. 18
      frontend/.storybook/main.js
  12. 10
      frontend/src/app/features/content/pages/content/content-event.component.ts
  13. 12
      frontend/src/app/features/content/pages/content/editor/content-field.component.ts
  14. 10
      frontend/src/app/features/content/pages/content/editor/content-section.component.ts
  15. 10
      frontend/src/app/features/content/pages/content/editor/field-copy-button.component.ts
  16. 12
      frontend/src/app/features/content/pages/content/inspecting/content-inspection.component.ts
  17. 10
      frontend/src/app/features/content/pages/content/references/content-references.component.ts
  18. 4
      frontend/src/app/features/content/pages/contents/custom-view-editor.component.ts
  19. 12
      frontend/src/app/features/content/shared/content-extension.component.ts
  20. 12
      frontend/src/app/features/content/shared/forms/array-editor.component.ts
  21. 12
      frontend/src/app/features/content/shared/forms/array-item.component.ts
  22. 10
      frontend/src/app/features/content/shared/forms/component.component.ts
  23. 10
      frontend/src/app/features/content/shared/forms/field-editor.component.ts
  24. 18
      frontend/src/app/features/content/shared/forms/iframe-editor.component.ts
  25. 12
      frontend/src/app/features/content/shared/list/content.component.ts
  26. 12
      frontend/src/app/features/content/shared/references/reference-dropdown.component.ts
  27. 4
      frontend/src/app/features/content/shared/references/reference-item.component.ts
  28. 2
      frontend/src/app/features/content/shared/references/references-checkboxes.component.html
  29. 10
      frontend/src/app/features/content/shared/references/references-checkboxes.component.ts
  30. 8
      frontend/src/app/features/content/shared/references/references-tag-converter.ts
  31. 2
      frontend/src/app/features/content/shared/references/references-tags.component.html
  32. 12
      frontend/src/app/features/content/shared/references/references-tags.component.ts
  33. 4
      frontend/src/app/features/dashboard/pages/cards/history-card.component.ts
  34. 12
      frontend/src/app/features/dashboard/pages/dashboard-config.component.ts
  35. 1
      frontend/src/app/features/rules/declarations.ts
  36. 3
      frontend/src/app/features/rules/module.ts
  37. 10
      frontend/src/app/features/rules/pages/simulator/rule-transition.component.ts
  38. 2
      frontend/src/app/features/rules/shared/actions/formattable-input.component.html
  39. 4
      frontend/src/app/features/rules/shared/actions/formattable-input.component.scss
  40. 2
      frontend/src/app/features/rules/shared/actions/formattable-input.component.ts
  41. 10
      frontend/src/app/features/rules/shared/actions/generic-action.component.ts
  42. 40
      frontend/src/app/features/rules/shared/triggers/completions-cache.ts
  43. 29
      frontend/src/app/features/rules/shared/triggers/content-changed-schema.component.html
  44. 8
      frontend/src/app/features/rules/shared/triggers/content-changed-schema.component.scss
  45. 47
      frontend/src/app/features/rules/shared/triggers/content-changed-schema.component.ts
  46. 63
      frontend/src/app/features/rules/shared/triggers/content-changed-trigger.component.html
  47. 6
      frontend/src/app/features/rules/shared/triggers/content-changed-trigger.component.scss
  48. 82
      frontend/src/app/features/rules/shared/triggers/content-changed-trigger.component.ts
  49. 4
      frontend/src/app/features/schemas/pages/schema/common/schema-edit-form.component.ts
  50. 4
      frontend/src/app/features/schemas/pages/schema/export/schema-export-form.component.ts
  51. 2
      frontend/src/app/features/schemas/pages/schema/fields/field-group.component.html
  52. 10
      frontend/src/app/features/schemas/pages/schema/fields/field.component.ts
  53. 9
      frontend/src/app/features/schemas/pages/schema/fields/sortable-field-list.component.html
  54. 10
      frontend/src/app/features/schemas/pages/schema/fields/types/boolean-validation.component.ts
  55. 10
      frontend/src/app/features/schemas/pages/schema/fields/types/date-time-validation.component.ts
  56. 10
      frontend/src/app/features/schemas/pages/schema/fields/types/number-ui.component.ts
  57. 10
      frontend/src/app/features/schemas/pages/schema/fields/types/string-ui.component.ts
  58. 12
      frontend/src/app/features/schemas/pages/schema/fields/types/string-validation.component.ts
  59. 2
      frontend/src/app/features/schemas/pages/schema/preview/schema-preview-urls-form.component.html
  60. 4
      frontend/src/app/features/schemas/pages/schema/preview/schema-preview-urls-form.component.ts
  61. 4
      frontend/src/app/features/schemas/pages/schema/rules/schema-field-rules-form.component.html
  62. 13
      frontend/src/app/features/schemas/pages/schema/rules/schema-field-rules-form.component.ts
  63. 6
      frontend/src/app/features/schemas/pages/schema/scripts/schema-scripts-form.component.ts
  64. 4
      frontend/src/app/features/schemas/pages/schema/ui/field-list.component.ts
  65. 4
      frontend/src/app/features/schemas/pages/schema/ui/schema-ui-form.component.ts
  66. 10
      frontend/src/app/features/settings/pages/backups/backup.component.ts
  67. 10
      frontend/src/app/features/settings/pages/clients/client.component.ts
  68. 4
      frontend/src/app/features/settings/pages/contributors/contributor-add-form.component.ts
  69. 4
      frontend/src/app/features/settings/pages/languages/language.component.ts
  70. 10
      frontend/src/app/features/settings/pages/roles/role.component.ts
  71. 4
      frontend/src/app/features/settings/pages/templates/template.component.ts
  72. 4
      frontend/src/app/features/settings/pages/workflows/workflow-diagram.component.ts
  73. 10
      frontend/src/app/features/settings/pages/workflows/workflow-step.component.ts
  74. 4
      frontend/src/app/features/settings/pages/workflows/workflow.component.ts
  75. 13
      frontend/src/app/framework/angular/avatar.component.ts
  76. 4
      frontend/src/app/framework/angular/forms/control-errors.component.ts
  77. 4
      frontend/src/app/framework/angular/forms/editors/checkbox-group.component.ts
  78. 5
      frontend/src/app/framework/angular/forms/editors/code-editor.component.html
  79. 23
      frontend/src/app/framework/angular/forms/editors/code-editor.component.scss
  80. 136
      frontend/src/app/framework/angular/forms/editors/code-editor.component.ts
  81. 99
      frontend/src/app/framework/angular/forms/editors/code-editor.stories.ts
  82. 9
      frontend/src/app/framework/angular/forms/editors/dropdown.component.ts
  83. 4
      frontend/src/app/framework/angular/forms/editors/radio-group.component.ts
  84. 8
      frontend/src/app/framework/angular/forms/editors/tag-editor.component.html
  85. 61
      frontend/src/app/framework/angular/forms/editors/tag-editor.component.ts
  86. 9
      frontend/src/app/framework/angular/forms/form-error.component.ts
  87. 7
      frontend/src/app/framework/angular/forms/progress-bar.component.ts
  88. 15
      frontend/src/app/framework/angular/helpers.ts
  89. 4
      frontend/src/app/framework/angular/image-source.directive.ts
  90. 4
      frontend/src/app/framework/angular/image-url.directive.ts
  91. 4
      frontend/src/app/framework/angular/language-selector.component.ts
  92. 12
      frontend/src/app/framework/angular/markdown.directive.spec.ts
  93. 6
      frontend/src/app/framework/angular/markdown.directive.ts
  94. 4
      frontend/src/app/framework/angular/pager.component.ts
  95. 4
      frontend/src/app/framework/angular/resized.directive.ts
  96. 4
      frontend/src/app/framework/angular/scroll-active.directive.ts
  97. 7
      frontend/src/app/framework/angular/template-wrapper.directive.ts
  98. 4
      frontend/src/app/framework/angular/title.component.ts
  99. 4
      frontend/src/app/framework/angular/video-player.component.ts
  100. 1
      frontend/src/app/framework/declarations.ts

1
backend/extensions/Squidex.Extensions/Actions/Webhook/WebhookAction.cs

@ -44,6 +44,7 @@ public sealed record WebhookAction : RuleAction
[Display(Name = "Headers (Optional)", Description = "The message headers in the format '[Key]=[Value]', one entry per line.")]
[Editor(RuleFieldEditor.TextArea)]
[Formattable]
public string Headers { get; set; }
[Display(Name = "Shared Secret", Description = "The shared secret that is used to calculate the payload signature.")]

1784
backend/src/Squidex.Domain.Apps.Core.Model/FieldDescriptions.Designer.cs

File diff suppressed because it is too large

11
backend/src/Squidex.Domain.Apps.Core.Model/FieldDescriptions.resx

@ -343,7 +343,13 @@
<value>The event timestamp.</value>
</data>
<data name="EventType" xml:space="preserve">
<value>The type of the event, e.g. 'Created' or 'Updated'.</value>
<value>The type of the event.</value>
</data>
<data name="ItemData" xml:space="preserve">
<value>The current item, if the field is part of an array.</value>
</data>
<data name="ItemDataField" xml:space="preserve">
<value>The current item, if the field is part of an array.</value>
</data>
<data name="JsonPath" xml:space="preserve">
<value>The path to the json value.</value>
@ -408,6 +414,9 @@
<data name="User" xml:space="preserve">
<value>Information about the current user.</value>
</data>
<data name="UserAppRole" xml:space="preserve">
<value>The role of the user within the current app.</value>
</data>
<data name="UserClaims" xml:space="preserve">
<value>The additional properties of this user.</value>
</data>

2
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentDataObject.cs

@ -139,7 +139,7 @@ public sealed class ContentDataObject : ObjectInstance
foreach (var (key, value) in contentData)
{
fieldProperties.Add(key, new ContentDataProperty(this, new ContentFieldObject(this, value, false)));
fieldProperties[key] = new ContentDataProperty(this, new ContentFieldObject(this, value, false));
}
}
}

2
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/IScriptDescriptor.cs

@ -9,7 +9,7 @@
namespace Squidex.Domain.Apps.Core.Scripting;
public delegate void AddDescription(JsonType type, string name, string description);
public delegate void AddDescription(JsonType type, string name, string description, string[]? allowedValues = null);
public interface IScriptDescriptor
{

2
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptExecutionContext.cs

@ -5,9 +5,9 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Diagnostics;
using Jint;
using Squidex.Infrastructure.Tasks;
using System.Diagnostics;
namespace Squidex.Domain.Apps.Core.Scripting;

62
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptingCompleter.cs

@ -73,6 +73,13 @@ public sealed class ScriptingCompleter
return new Process(descriptors, dataSchema.Flatten()).ContentTrigger();
}
public IReadOnlyList<ScriptingValue> FieldRule(FilterSchema dataSchema)
{
Guard.NotNull(dataSchema);
return new Process(descriptors, dataSchema.Flatten()).FieldRule();
}
public IReadOnlyList<ScriptingValue> AssetScript()
{
return new Process(descriptors).AssetScript();
@ -112,11 +119,6 @@ public sealed class ScriptingCompleter
this.dataSchema = dataSchema;
}
private IReadOnlyList<ScriptingValue> Build()
{
return result.Values.OrderBy(x => x.Path).ToList();
}
public IReadOnlyList<ScriptingValue> SchemaTrigger()
{
AddHelpers(ScriptScope.SchemaTrigger | ScriptScope.Async);
@ -205,6 +207,41 @@ public sealed class ScriptingCompleter
return Build();
}
public IReadOnlyList<ScriptingValue> FieldRule()
{
AddObject("user", FieldDescriptions.User, () =>
{
AddString("id",
FieldDescriptions.UserId);
AddString("email",
FieldDescriptions.UserEmail);
AddString("displayName",
FieldDescriptions.UserDisplayName);
AddString("role",
FieldDescriptions.UserAppRole);
});
AddObject("data", FieldDescriptions.ContentData, () =>
{
AddData();
});
AddObject("itemData", FieldDescriptions.ItemData, () =>
{
AddAny("my-field", FieldDescriptions.ItemDataField);
});
return Build();
}
private IReadOnlyList<ScriptingValue> Build()
{
return result.Values.OrderBy(x => x.Path).ToList();
}
private void AddHelpers(ScriptScope scope)
{
foreach (var descriptor in descriptors)
@ -219,7 +256,13 @@ public sealed class ScriptingCompleter
{
var propertyType = Nullable.GetUnderlyingType(propertyTypeOrNullable) ?? propertyTypeOrNullable;
if (propertyType.IsEnum ||
if (propertyType.IsEnum)
{
var allowedValues = Enum.GetValues(propertyType).OfType<object>().Select(x => x.ToString()!).Order().ToArray();
Add(JsonType.String, name, description, allowedValues);
}
else if (
propertyType == typeof(string) ||
propertyType == typeof(DomainId) ||
propertyType == typeof(Instant) ||
@ -410,7 +453,7 @@ public sealed class ScriptingCompleter
Add(JsonType.String, name, description);
}
private void Add(JsonType type, string? name, string? description)
private void Add(JsonType type, string? name, string? description, string[]? allowedValues = null)
{
var parts = name?.Split('.') ?? Array.Empty<string>();
@ -426,7 +469,10 @@ public sealed class ScriptingCompleter
var path = string.Concat(prefixes.Reverse());
result[path] = new ScriptingValue(path, type, description);
result[path] = new ScriptingValue(path, type, description)
{
AllowedValues = allowedValues
};
for (int i = 0; i < parts.Length; i++)
{

5
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptingValue.cs

@ -9,4 +9,7 @@
namespace Squidex.Domain.Apps.Core.Scripting;
public sealed record ScriptingValue(string Path, JsonType Type, string? Description);
public sealed record ScriptingValue(string Path, JsonType Type, string? Description)
{
public string[]? AllowedValues { get; init; }
}

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

@ -309,7 +309,7 @@ public sealed class SchemasController : ApiController
}
[HttpGet]
[Route("apps/{app}/schemas/{schema}/completion")]
[Route("apps/{app}/schemas/{schema}/completion/content-scripts")]
[ApiPermissionOrAnonymous]
[ApiCosts(1)]
[ApiExplorerSettings(IgnoreApi = true)]
@ -322,7 +322,7 @@ public sealed class SchemasController : ApiController
}
[HttpGet]
[Route("apps/{app}/schemas/{schema}/completion/triggers")]
[Route("apps/{app}/schemas/{schema}/completion/content-triggers")]
[ApiPermissionOrAnonymous]
[ApiCosts(1)]
[ApiExplorerSettings(IgnoreApi = true)]
@ -334,6 +334,19 @@ public sealed class SchemasController : ApiController
return Ok(completion);
}
[HttpGet]
[Route("apps/{app}/schemas/{schema}/completion/field-rules")]
[ApiPermissionOrAnonymous]
[ApiCosts(1)]
[ApiExplorerSettings(IgnoreApi = true)]
public async Task<IActionResult> GetScriptFieldRulesCompletion(string app, string schema,
[FromServices] ScriptingCompleter completer)
{
var completion = completer.FieldRule(await BuildModel());
return Ok(completion);
}
[HttpGet]
[Route("apps/{app}/schemas/{schema}/filters")]
[ApiPermissionOrAnonymous]

21
backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/ScriptingCompleterTests.cs

@ -173,6 +173,27 @@ public class ScriptingCompleterTests
});
}
[Fact]
public void Should_describe_field_rule()
{
var actual = sut.FieldRule(dataSchema);
AssertCompletion(actual,
new[]
{
"data",
"data['my-field']",
"data['my-field'].iv",
"itemData",
"itemData['my-field']",
"user",
"user.displayName",
"user.email",
"user.id",
"user.role",
});
}
[Fact]
public void Should_describe_asset_trigger()
{

18
frontend/.storybook/main.js

@ -5,18 +5,24 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
const customConfig = require('./../src/config/webpack.config');
module.exports = {
"stories": [
stories: [
"../src/**/*.stories.mdx",
"../src/**/*.stories.@(js|jsx|ts|tsx)"
],
"addons": [
addons: [
"@storybook/addon-links",
"@storybook/addon-essentials",
"@storybook/addon-interactions"
],
"framework": "@storybook/angular",
"core": {
"builder": "@storybook/builder-webpack5"
}
framework: "@storybook/angular",
core: {
builder: "@storybook/builder-webpack5"
},
webpackFinal: async (config) => {
customConfig(config, {}, {});
return config;
},
}

10
frontend/src/app/features/content/pages/content/content-event.component.ts

@ -5,8 +5,8 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core';
import { ContentDto, HistoryEventDto } from '@app/shared';
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
import { ContentDto, HistoryEventDto, TypedSimpleChanges } from '@app/shared';
@Component({
selector: 'sqx-content-event[content][event]',
@ -14,7 +14,7 @@ import { ContentDto, HistoryEventDto } from '@app/shared';
templateUrl: './content-event.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ContentEventComponent implements OnChanges {
export class ContentEventComponent {
@Output()
public dataLoad = new EventEmitter();
@ -29,8 +29,8 @@ export class ContentEventComponent implements OnChanges {
public canLoadOrCompare = false;
public ngOnChanges(changes: SimpleChanges) {
if (changes['event']) {
public ngOnChanges(changes: TypedSimpleChanges<this>) {
if (changes.event) {
this.canLoadOrCompare =
(this.event.eventType === 'ContentUpdatedEvent' ||
this.event.eventType === 'ContentCreatedEventV2') &&

12
frontend/src/app/features/content/pages/content/editor/content-field.component.ts

@ -5,16 +5,16 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Component, EventEmitter, HostBinding, Input, OnChanges, Output, SimpleChanges } from '@angular/core';
import { Component, EventEmitter, HostBinding, Input, Output } from '@angular/core';
import { Observable } from 'rxjs';
import { AppLanguageDto, AppsState, changed$, disabled$, EditContentForm, FieldForm, invalid$, LocalStoreService, SchemaDto, Settings, TranslationsService } from '@app/shared';
import { AppLanguageDto, AppsState, changed$, disabled$, EditContentForm, FieldForm, invalid$, LocalStoreService, SchemaDto, Settings, TranslationsService, TypedSimpleChanges } from '@app/shared';
@Component({
selector: 'sqx-content-field[form][formContext][formLevel][formModel][language][languages][schema]',
styleUrls: ['./content-field.component.scss'],
templateUrl: './content-field.component.html',
})
export class ContentFieldComponent implements OnChanges {
export class ContentFieldComponent {
@Output()
public languageChange = new EventEmitter<AppLanguageDto>();
@ -74,15 +74,15 @@ export class ContentFieldComponent implements OnChanges {
) {
}
public ngOnChanges(changes: SimpleChanges) {
public ngOnChanges(changes: TypedSimpleChanges<this>) {
this.showAllControls = this.localStore.getBoolean(this.showAllControlsKey());
if (changes['formModel'] && this.formModel) {
if (changes.formModel && this.formModel) {
this.isInvalid = invalid$(this.formModel.form);
this.isDisabled = disabled$(this.formModel.form);
}
if ((changes['formModel'] || changes['formModelCompare']) && this.formModelCompare) {
if ((changes.formModel || changes.formModelCompare) && this.formModelCompare) {
this.isDifferent = changed$(this.formModel.form, this.formModelCompare.form);
}
}

10
frontend/src/app/features/content/pages/content/editor/content-section.component.ts

@ -5,8 +5,8 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core';
import { AppLanguageDto, EditContentForm, FieldForm, FieldSection, LocalStoreService, RootFieldDto, SchemaDto, Settings, StatefulComponent } from '@app/shared';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, Output } from '@angular/core';
import { AppLanguageDto, EditContentForm, FieldForm, FieldSection, LocalStoreService, RootFieldDto, SchemaDto, Settings, StatefulComponent, TypedSimpleChanges } from '@app/shared';
interface State {
// The when the section is collapsed.
@ -19,7 +19,7 @@ interface State {
templateUrl: './content-section.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ContentSectionComponent extends StatefulComponent<State> implements OnChanges {
export class ContentSectionComponent extends StatefulComponent<State> {
@Output()
public languageChange = new EventEmitter<AppLanguageDto>();
@ -64,8 +64,8 @@ export class ContentSectionComponent extends StatefulComponent<State> implements
});
}
public ngOnChanges(changes: SimpleChanges) {
if (changes['formSection' || changes['schema']]) {
public ngOnChanges(changes: TypedSimpleChanges<this>) {
if (changes.formSection || changes.schema) {
if (this.formSection?.separator && this.schema) {
const isCollapsed = this.localStore.getBoolean(this.isCollapsedKey());

10
frontend/src/app/features/content/pages/content/editor/field-copy-button.component.ts

@ -5,15 +5,15 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
import { AppLanguageDto, FieldForm, ModalModel } from '@app/shared';
import { Component, Input } from '@angular/core';
import { AppLanguageDto, FieldForm, ModalModel, TypedSimpleChanges } from '@app/shared';
@Component({
selector: 'sqx-field-copy-button[formModel][languages]',
styleUrls: ['./field-copy-button.component.scss'],
templateUrl: './field-copy-button.component.html',
})
export class FieldCopyButtonComponent implements OnChanges {
export class FieldCopyButtonComponent {
@Input()
public formModel!: FieldForm;
@ -31,8 +31,8 @@ export class FieldCopyButtonComponent implements OnChanges {
return this.formModel.field.isLocalizable && this.languages.length > 1;
}
public ngOnChanges(changes: SimpleChanges) {
if (changes['languages']) {
public ngOnChanges(changes: TypedSimpleChanges<this>) {
if (changes.languages) {
this.setCopySource(this.languages[0]?.iso2Code);
}
}

12
frontend/src/app/features/content/pages/content/inspecting/content-inspection.component.ts

@ -5,10 +5,10 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { ChangeDetectorRef, Component, Input, OnChanges, OnDestroy, SimpleChanges } from '@angular/core';
import { ChangeDetectorRef, Component, Input, OnDestroy } from '@angular/core';
import { BehaviorSubject, combineLatest, of } from 'rxjs';
import { filter, map, switchMap } from 'rxjs/operators';
import { AppLanguageDto, ContentDto, ContentsService, ContentsState, ErrorDto, ToolbarService } from '@app/shared';
import { AppLanguageDto, ContentDto, ContentsService, ContentsState, ErrorDto, ToolbarService, TypedSimpleChanges } from '@app/shared';
type Mode = 'Content' | 'Data' | 'FlatData';
@ -17,7 +17,7 @@ type Mode = 'Content' | 'Data' | 'FlatData';
styleUrls: ['./content-inspection.component.scss'],
templateUrl: './content-inspection.component.html',
})
export class ContentInspectionComponent implements OnChanges, OnDestroy {
export class ContentInspectionComponent implements OnDestroy {
private languageChanges$ = new BehaviorSubject<AppLanguageDto | null>(null);
@Input()
@ -69,12 +69,12 @@ export class ContentInspectionComponent implements OnChanges, OnDestroy {
this.toolbar.remove(this);
}
public ngOnChanges(changes: SimpleChanges) {
if (changes['language']) {
public ngOnChanges(changes: TypedSimpleChanges<this>) {
if (changes.language) {
this.languageChanges$.next(this.language);
}
if (changes['content']) {
if (changes.content) {
this.updateActions();
}
}

10
frontend/src/app/features/content/pages/content/references/content-references.component.ts

@ -5,8 +5,8 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges } from '@angular/core';
import { AppLanguageDto, ComponentContentsState, ContentDto, QuerySynchronizer, Router2State, ToolbarService } from '@app/shared';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy, OnInit } from '@angular/core';
import { AppLanguageDto, ComponentContentsState, ContentDto, QuerySynchronizer, Router2State, ToolbarService, TypedSimpleChanges } from '@app/shared';
@Component({
selector: 'sqx-content-references[content][language][languages]',
@ -17,7 +17,7 @@ import { AppLanguageDto, ComponentContentsState, ContentDto, QuerySynchronizer,
Router2State, ComponentContentsState,
],
})
export class ContentReferencesComponent implements OnChanges, OnInit, OnDestroy {
export class ContentReferencesComponent implements OnInit, OnDestroy {
@Input()
public content!: ContentDto;
@ -56,8 +56,8 @@ export class ContentReferencesComponent implements OnChanges, OnInit, OnDestroy
});
}
public ngOnChanges(changes: SimpleChanges) {
if (changes['content'] || changes['mode']) {
public ngOnChanges(changes: TypedSimpleChanges<this>) {
if (changes.content || changes.mode) {
this.contentsState.schema = { name: this.content.schemaName };
const initial =

4
frontend/src/app/features/content/pages/contents/custom-view-editor.component.ts

@ -6,7 +6,7 @@
*/
import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop';
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Output } from '@angular/core';
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
import { TableField } from '@app/shared';
@Component({
@ -15,7 +15,7 @@ import { TableField } from '@app/shared';
templateUrl: './custom-view-editor.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CustomViewEditorComponent implements OnChanges {
export class CustomViewEditorComponent {
@Output()
public reset = new EventEmitter();

12
frontend/src/app/features/content/shared/content-extension.component.ts

@ -5,10 +5,10 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { ChangeDetectionStrategy, Component, ElementRef, HostListener, Input, OnChanges, SimpleChanges, ViewChild } from '@angular/core';
import { ChangeDetectionStrategy, Component, ElementRef, HostListener, Input, ViewChild } from '@angular/core';
import { Router } from '@angular/router';
import { ApiUrlConfig, ResourceOwner, Types } from '@app/framework/internal';
import { AppsState, AuthService, computeEditorUrl, ContentDto, SchemaDto } from '@app/shared';
import { AppsState, AuthService, computeEditorUrl, ContentDto, SchemaDto, TypedSimpleChanges } from '@app/shared';
@Component({
selector: 'sqx-content-extension[content][contentSchema]',
@ -16,7 +16,7 @@ import { AppsState, AuthService, computeEditorUrl, ContentDto, SchemaDto } from
templateUrl: './content-extension.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ContentExtensionComponent extends ResourceOwner implements OnChanges {
export class ContentExtensionComponent extends ResourceOwner {
private readonly context: any;
private isInitialized = false;
@ -50,13 +50,13 @@ export class ContentExtensionComponent extends ResourceOwner implements OnChange
};
}
public ngOnChanges(changes: SimpleChanges) {
if (changes['contentSchema']) {
public ngOnChanges(changes: TypedSimpleChanges<this>) {
if (changes.contentSchema) {
this.context['schemaName'] = this.contentSchema?.name;
this.context['schemaId'] = this.contentSchema?.id;
}
if (changes['content']) {
if (changes.content) {
this.sendContent();
}
}

12
frontend/src/app/features/content/shared/forms/array-editor.component.ts

@ -6,11 +6,11 @@
*/
import { CdkDragDrop } from '@angular/cdk/drag-drop';
import { ChangeDetectionStrategy, Component, Input, OnChanges, QueryList, SimpleChanges, ViewChildren } from '@angular/core';
import { ChangeDetectionStrategy, Component, Input, QueryList, ViewChildren } from '@angular/core';
import { VirtualScrollerComponent } from 'ngx-virtual-scroller';
import { combineLatest, Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { AppLanguageDto, ComponentsFieldPropertiesDto, disabled$, EditContentForm, FieldArrayForm, LocalStoreService, ModalModel, ObjectFormBase, SchemaDto, Settings, sorted, Types } from '@app/shared';
import { AppLanguageDto, ComponentsFieldPropertiesDto, disabled$, EditContentForm, FieldArrayForm, LocalStoreService, ModalModel, ObjectFormBase, SchemaDto, Settings, sorted, TypedSimpleChanges, Types } from '@app/shared';
import { ArrayItemComponent } from './array-item.component';
@Component({
@ -19,7 +19,7 @@ import { ArrayItemComponent } from './array-item.component';
templateUrl: './array-editor.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ArrayEditorComponent implements OnChanges {
export class ArrayEditorComponent {
@Input()
public form!: EditContentForm;
@ -71,8 +71,8 @@ export class ArrayEditorComponent implements OnChanges {
) {
}
public ngOnChanges(changes: SimpleChanges) {
if (changes['formModel']) {
public ngOnChanges(changes: TypedSimpleChanges<this>) {
if (changes.formModel) {
const maxItems = this.formModel.field.properties['maxItems'] || Number.MAX_VALUE;
if (Types.is(this.formModel.field.properties, ComponentsFieldPropertiesDto)) {
@ -90,7 +90,7 @@ export class ArrayEditorComponent implements OnChanges {
}));
}
if (changes['formModel'] || changes['formLevel']) {
if (changes.formModel || changes.formLevel) {
this.isCollapsedInitial = this.localStore.getBoolean(this.isCollapsedKey()) || this.formLevel > 0;
}
}

12
frontend/src/app/features/content/shared/forms/array-item.component.ts

@ -5,10 +5,10 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Output, QueryList, SimpleChanges, ViewChildren } from '@angular/core';
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output, QueryList, ViewChildren } from '@angular/core';
import { Observable, of } from 'rxjs';
import { map } from 'rxjs/operators';
import { AppLanguageDto, ComponentForm, EditContentForm, FieldDto, FieldFormatter, FieldSection, invalid$, ObjectFormBase, RootFieldDto, Types, valueProjection$ } from '@app/shared';
import { AppLanguageDto, ComponentForm, EditContentForm, FieldDto, FieldFormatter, FieldSection, invalid$, ObjectFormBase, RootFieldDto, TypedSimpleChanges, Types, valueProjection$ } from '@app/shared';
import { ComponentSectionComponent } from './component-section.component';
@Component({
@ -17,7 +17,7 @@ import { ComponentSectionComponent } from './component-section.component';
templateUrl: './array-item.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ArrayItemComponent implements OnChanges {
export class ArrayItemComponent {
@Output()
public itemRemove = new EventEmitter();
@ -81,8 +81,8 @@ export class ArrayItemComponent implements OnChanges {
return this.formModel.collapsedChanges;
}
public ngOnChanges(changes: SimpleChanges) {
if (changes['formModel']) {
public ngOnChanges(changes: TypedSimpleChanges<this>) {
if (changes.formModel) {
this.isInvalidForm = invalid$(this.formModel.form);
if (Types.is(this.formModel, ComponentForm)) {
@ -94,7 +94,7 @@ export class ArrayItemComponent implements OnChanges {
this.title = valueProjection$(this.formModel.form, () => getTitle(this.formModel));
}
if (changes['formModel'] || changes['isCollapsedInitial']) {
if (changes.formModel || changes.isCollapsedInitial) {
if (this.isCollapsedInitial && this.formModel.collapsed === null) {
this.collapse();
}

10
frontend/src/app/features/content/shared/forms/component.component.ts

@ -5,9 +5,9 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnChanges, QueryList, SimpleChanges, ViewChildren } from '@angular/core';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, QueryList, ViewChildren } from '@angular/core';
import { Observable } from 'rxjs';
import { AppLanguageDto, ComponentFieldPropertiesDto, ComponentForm, disabled$, EditContentForm, FieldDto, FieldSection, ModalModel, ResourceOwner, SchemaDto, Types } from '@app/shared';
import { AppLanguageDto, ComponentFieldPropertiesDto, ComponentForm, disabled$, EditContentForm, FieldDto, FieldSection, ModalModel, ResourceOwner, SchemaDto, TypedSimpleChanges, Types } from '@app/shared';
import { ComponentSectionComponent } from './component-section.component';
@Component({
@ -16,7 +16,7 @@ import { ComponentSectionComponent } from './component-section.component';
templateUrl: './component.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ComponentComponent extends ResourceOwner implements OnChanges {
export class ComponentComponent extends ResourceOwner {
@Input()
public canUnset?: boolean | null;
@ -55,8 +55,8 @@ export class ComponentComponent extends ResourceOwner implements OnChanges {
super();
}
public ngOnChanges(changes: SimpleChanges) {
if (changes['formModel']) {
public ngOnChanges(changes: TypedSimpleChanges<this>) {
if (changes.formModel) {
this.unsubscribeAll();
this.isDisabled = disabled$(this.formModel.form);

10
frontend/src/app/features/content/shared/forms/field-editor.component.ts

@ -5,17 +5,17 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Component, ElementRef, EventEmitter, Input, OnChanges, Output, SimpleChanges, ViewChild } from '@angular/core';
import { Component, ElementRef, EventEmitter, Input, Output, ViewChild } from '@angular/core';
import { AbstractControl } from '@angular/forms';
import { Observable } from 'rxjs';
import { AbstractContentForm, AppLanguageDto, EditContentForm, FieldDto, hasNoValue$, MathHelper, Types } from '@app/shared';
import { AbstractContentForm, AppLanguageDto, EditContentForm, FieldDto, hasNoValue$, MathHelper, TypedSimpleChanges, Types } from '@app/shared';
@Component({
selector: 'sqx-field-editor[form][formContext][formLevel][formModel][isComparing][language][languages]',
styleUrls: ['./field-editor.component.scss'],
templateUrl: './field-editor.component.html',
})
export class FieldEditorComponent implements OnChanges {
export class FieldEditorComponent {
public readonly uniqueId = MathHelper.guid();
@Output()
@ -65,8 +65,8 @@ export class FieldEditorComponent implements OnChanges {
return this.formModel.form;
}
public ngOnChanges(changes: SimpleChanges) {
if (changes['formModel']) {
public ngOnChanges(changes: TypedSimpleChanges<this>) {
if (changes.formModel) {
this.isEmpty = hasNoValue$(this.formModel.form);
}
}

18
frontend/src/app/features/content/shared/forms/iframe-editor.component.ts

@ -5,10 +5,10 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, EventEmitter, HostListener, Input, OnChanges, OnDestroy, Output, Renderer2, SimpleChanges, ViewChild } from '@angular/core';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, EventEmitter, HostListener, Input, OnDestroy, Output, Renderer2, ViewChild } from '@angular/core';
import { AbstractControl } from '@angular/forms';
import { Router } from '@angular/router';
import { DialogModel, DialogService, disabled$, StatefulComponent, Types, value$ } from '@app/framework';
import { DialogModel, DialogService, disabled$, StatefulComponent, TypedSimpleChanges, Types, value$ } from '@app/framework';
import { AppLanguageDto, AppsState, AssetDto, computeEditorUrl, ContentDto } from '@app/shared';
interface State {
@ -22,7 +22,7 @@ interface State {
templateUrl: './iframe-editor.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class IFrameEditorComponent extends StatefulComponent<State> implements OnChanges, OnDestroy {
export class IFrameEditorComponent extends StatefulComponent<State> implements OnDestroy {
private value: any;
private isInitialized = false;
private isDisabled = false;
@ -99,24 +99,24 @@ export class IFrameEditorComponent extends StatefulComponent<State> implements O
this.toggleFullscreen(false);
}
public ngOnChanges(changes: SimpleChanges) {
if (changes['formValue']) {
public ngOnChanges(changes: TypedSimpleChanges<this>) {
if (changes.formValue) {
this.sendFormValue();
}
if (changes['formIndex']) {
if (changes.formIndex) {
this.sendMoved();
}
if (changes['expanded']) {
if (changes.isExpanded) {
this.sendExpanded();
}
if (changes['language']) {
if (changes.language) {
this.sendLanguage();
}
if (changes['formControlBinding']) {
if (changes.formControlBinding) {
this.unsubscribeAll();
const control = this.formControlBinding;

12
frontend/src/app/features/content/shared/list/content.component.ts

@ -5,8 +5,8 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, OnChanges, Output, QueryList, SimpleChanges, ViewChildren } from '@angular/core';
import { AppLanguageDto, ContentDto, ContentListFieldComponent, ContentsState, ModalModel, PatchContentForm, SchemaDto, TableField, TableSettings } from '@app/shared';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, Output, QueryList, ViewChildren } from '@angular/core';
import { AppLanguageDto, ContentDto, ContentListFieldComponent, ContentsState, ModalModel, PatchContentForm, SchemaDto, TableField, TableSettings, TypedSimpleChanges } from '@app/shared';
/* tslint:disable: component-selector */
@ -16,7 +16,7 @@ import { AppLanguageDto, ContentDto, ContentListFieldComponent, ContentsState, M
templateUrl: './content.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ContentComponent implements OnChanges {
export class ContentComponent {
@Output()
public clone = new EventEmitter();
@ -74,12 +74,12 @@ export class ContentComponent implements OnChanges {
) {
}
public ngOnChanges(changes: SimpleChanges) {
if (changes['content']) {
public ngOnChanges(changes: TypedSimpleChanges<this>) {
if (changes.content) {
this.patchAllowed = this.content.canUpdate;
}
if (this.patchAllowed && (changes['listFields'] || changes['language'])) {
if (this.patchAllowed && (changes.tableFields || changes.language)) {
this.patchForm = new PatchContentForm(this.tableFields, this.language);
}
}

12
frontend/src/app/features/content/shared/references/reference-dropdown.component.ts

@ -5,11 +5,11 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, forwardRef, Input, OnChanges, SimpleChanges } from '@angular/core';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, forwardRef, Input } from '@angular/core';
import { NG_VALUE_ACCESSOR, UntypedFormControl } from '@angular/forms';
import { Observable } from 'rxjs';
import { ContentsDto } from '@app/shared';
import { ContentDto, getContentValue, LanguageDto, LocalizerService, ResolveContents, StatefulControlComponent, Types, value$ } from '@app/shared/internal';
import { ContentDto, getContentValue, LanguageDto, LocalizerService, ResolveContents, StatefulControlComponent, TypedSimpleChanges, Types, value$ } from '@app/shared/internal';
export const SQX_REFERENCE_DROPDOWN_CONTROL_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => ReferenceDropdownComponent), multi: true,
@ -39,7 +39,7 @@ const NO_EMIT = { emitEvent: false };
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ReferenceDropdownComponent extends StatefulControlComponent<State, ReadonlyArray<string> | string> implements OnChanges {
export class ReferenceDropdownComponent extends StatefulControlComponent<State, ReadonlyArray<string> | string> {
private readonly contents: ContentDto[] = [];
private isOpenedBefore = false;
private isLoadingFailed = false;
@ -96,14 +96,14 @@ export class ReferenceDropdownComponent extends StatefulControlComponent<State,
}));
}
public ngOnChanges(changes: SimpleChanges) {
if (changes['schemaId']) {
public ngOnChanges(changes: TypedSimpleChanges<this>) {
if (changes.schemaId) {
this.contents.clear();
this.resetState();
}
if (changes['language'] || changes['schemaId']) {
if (changes.language || changes.schemaId) {
this.resetContentNames(true);
}
}

4
frontend/src/app/features/content/shared/references/reference-item.component.ts

@ -7,7 +7,7 @@
/* tslint:disable: component-selector */
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Output } from '@angular/core';
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
import { ContentDto, getContentValue, LanguageDto, META_FIELDS } from '@app/shared';
@Component({
@ -16,7 +16,7 @@ import { ContentDto, getContentValue, LanguageDto, META_FIELDS } from '@app/shar
templateUrl: './reference-item.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ReferenceItemComponent implements OnChanges {
export class ReferenceItemComponent {
public readonly metaFields = META_FIELDS;
@Output()

2
frontend/src/app/features/content/shared/references/references-checkboxes.component.html

@ -1,3 +1,3 @@
<sqx-checkbox-group [formControl]="control"
[values]="snapshot.converter.suggestions">
[values]="snapshot.converter.tags">
</sqx-checkbox-group>

10
frontend/src/app/features/content/shared/references/references-checkboxes.component.ts

@ -5,9 +5,9 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, forwardRef, Input, OnChanges, SimpleChanges } from '@angular/core';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, forwardRef, Input } from '@angular/core';
import { NG_VALUE_ACCESSOR, UntypedFormControl } from '@angular/forms';
import { AppsState, ContentDto, ContentsService, LanguageDto, LocalizerService, StatefulControlComponent, UIOptions } from '@app/shared/internal';
import { AppsState, ContentDto, ContentsService, LanguageDto, LocalizerService, StatefulControlComponent, TypedSimpleChanges, UIOptions } from '@app/shared/internal';
import { ReferencesTagsConverter } from './references-tag-converter';
export const SQX_REFERENCES_CHECKBOXES_CONTROL_VALUE_ACCESSOR: any = {
@ -30,7 +30,7 @@ const NO_EMIT = { emitEvent: false };
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ReferencesCheckboxesComponent extends StatefulControlComponent<State, ReadonlyArray<string>> implements OnChanges {
export class ReferencesCheckboxesComponent extends StatefulControlComponent<State, ReadonlyArray<string>> {
private readonly itemCount: number;
private contentItems: ReadonlyArray<ContentDto> | null = null;
@ -75,8 +75,8 @@ export class ReferencesCheckboxesComponent extends StatefulControlComponent<Stat
}));
}
public ngOnChanges(changes: SimpleChanges) {
if (changes['schemaId']) {
public ngOnChanges(changes: TypedSimpleChanges<this>) {
if (changes.schemaId) {
this.resetState();
if (this.isValid) {

8
frontend/src/app/features/content/shared/references/references-tag-converter.ts

@ -8,22 +8,22 @@
import { ContentDto, getContentValue, LanguageDto, LocalizerService, TagConverter, TagValue } from '@app/shared/internal';
export class ReferencesTagsConverter implements TagConverter {
public suggestions: ReadonlyArray<TagValue> = [];
public tags: ReadonlyArray<TagValue> = [];
constructor(language: LanguageDto, contents: ReadonlyArray<ContentDto>,
private readonly localizer: LocalizerService,
) {
this.suggestions = this.createTags(language, contents);
this.tags = this.createTags(language, contents);
}
public convertInput(input: string) {
const result = this.suggestions.find(x => x.name === input);
const result = this.tags.find(x => x.name === input);
return result || null;
}
public convertValue(value: any) {
const result = this.suggestions.find(x => x.id === value);
const result = this.tags.find(x => x.id === value);
return result || null;
}

2
frontend/src/app/features/content/shared/references/references-tags.component.html

@ -4,6 +4,6 @@
[allowOpen]="true"
[formControl]="control"
[itemConverter]="snapshot.converter"
[itemsSource]="snapshot.converter.suggestions"
[itemsSource]="snapshot.converter.tags"
[itemsSourceLoading]="snapshot.isLoading">
</sqx-tag-editor>

12
frontend/src/app/features/content/shared/references/references-tags.component.ts

@ -5,10 +5,10 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, forwardRef, Input, OnChanges, SimpleChanges } from '@angular/core';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, forwardRef, Input } from '@angular/core';
import { NG_VALUE_ACCESSOR, UntypedFormControl } from '@angular/forms';
import { Observable } from 'rxjs';
import { Types } from '@app/framework';
import { TypedSimpleChanges, Types } from '@app/framework';
import { ContentDto, ContentsDto, LanguageDto, LocalizerService, ResolveContents, StatefulControlComponent } from '@app/shared/internal';
import { ReferencesTagsConverter } from './references-tag-converter';
@ -35,7 +35,7 @@ const NO_EMIT = { emitEvent: false };
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ReferencesTagsComponent extends StatefulControlComponent<State, ReadonlyArray<string>> implements OnChanges {
export class ReferencesTagsComponent extends StatefulControlComponent<State, ReadonlyArray<string>> {
private readonly contents: ContentDto[] = [];
private isOpenedBefore = false;
private isLoadingFailed = false;
@ -81,14 +81,14 @@ export class ReferencesTagsComponent extends StatefulControlComponent<State, Rea
}));
}
public ngOnChanges(changes: SimpleChanges) {
if (changes['schemaId']) {
public ngOnChanges(changes: TypedSimpleChanges<this>) {
if (changes.schemaId) {
this.contents.clear();
this.resetState();
}
if (changes['language'] || changes['schemaId']) {
if (changes.language || changes.schemaId) {
this.resetConverterState(true);
}
}

4
frontend/src/app/features/dashboard/pages/cards/history-card.component.ts

@ -5,7 +5,7 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { ChangeDetectionStrategy, Component, Input, OnChanges } from '@angular/core';
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { Observable } from 'rxjs';
import { AppDto, HistoryEventDto, HistoryService } from '@app/shared';
@ -15,7 +15,7 @@ import { AppDto, HistoryEventDto, HistoryService } from '@app/shared';
templateUrl: './history-card.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class HistoryCardComponent implements OnChanges {
export class HistoryCardComponent {
@Input()
public app!: AppDto;

12
frontend/src/app/features/dashboard/pages/dashboard-config.component.ts

@ -5,17 +5,17 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core';
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { GridsterItem } from 'angular-gridster2';
import { take } from 'rxjs/operators';
import { AppDto, AppsState, AuthService, DialogModel, DialogService, LocalizerService, ModalModel, Types, UIState } from '@app/shared';
import { AppDto, AppsState, AuthService, DialogModel, DialogService, LocalizerService, ModalModel, TypedSimpleChanges, Types, UIState } from '@app/shared';
@Component({
selector: 'sqx-dashboard-config[app][config]',
styleUrls: ['./dashboard-config.component.scss'],
templateUrl: './dashboard-config.component.html',
})
export class DashboardConfigComponent implements OnChanges {
export class DashboardConfigComponent {
@Input()
public app!: AppDto;
@ -50,8 +50,8 @@ export class DashboardConfigComponent implements OnChanges {
) {
}
public ngOnChanges(changes: SimpleChanges) {
if (changes['configAvailable']) {
public ngOnChanges(changes: TypedSimpleChanges<this>) {
if (changes.configAvailable) {
this.configOptions = this.configAvailable.map(item => {
const name = this.localizer.getOrKey(item.name);
@ -59,7 +59,7 @@ export class DashboardConfigComponent implements OnChanges {
}).sortByString(x => x.name);
}
if (changes['app']) {
if (changes.app) {
this.uiState.getUser('dashboard.grid', this.configDefaults).pipe(take(1))
.subscribe(dto => {
this.setConfig(dto);

1
frontend/src/app/features/rules/declarations.ts

@ -20,6 +20,7 @@ export * from './shared/rule-icon.component';
export * from './shared/pipes';
export * from './shared/triggers/asset-changed-trigger.component';
export * from './shared/triggers/comment-trigger.component';
export * from './shared/triggers/content-changed-schema.component';
export * from './shared/triggers/content-changed-trigger.component';
export * from './shared/triggers/schema-changed-trigger.component';
export * from './shared/triggers/usage-trigger.component';

3
frontend/src/app/features/rules/module.ts

@ -8,7 +8,7 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HelpComponent, RuleMustExistGuard, SqxFrameworkModule, SqxSharedModule } from '@app/shared';
import { AssetChangedTriggerComponent, CommentTriggerComponent, ContentChangedTriggerComponent, GenericActionComponent, RuleClassPipe, RuleComponent, RuleElementComponent, RuleEventsPageComponent, RuleIconComponent, RuleSimulatorPageComponent, RulesPageComponent, RuleTransitionComponent, SchemaChangedTriggerComponent, SimulatedRuleEventStatusPipe, UsageTriggerComponent } from './declarations';
import { AssetChangedTriggerComponent, CommentTriggerComponent, ContentChangedSchemaComponent, ContentChangedTriggerComponent, GenericActionComponent, RuleClassPipe, RuleComponent, RuleElementComponent, RuleEventsPageComponent, RuleIconComponent, RuleSimulatorPageComponent, RulesPageComponent, RuleTransitionComponent, SchemaChangedTriggerComponent, SimulatedRuleEventStatusPipe, UsageTriggerComponent } from './declarations';
import { RuleEventComponent } from './pages/events/rule-event.component';
import { RulePageComponent } from './pages/rule/rule-page.component';
import { SimulatedRuleEventComponent } from './pages/simulator/simulated-rule-event.component';
@ -68,6 +68,7 @@ const routes: Routes = [
declarations: [
AssetChangedTriggerComponent,
CommentTriggerComponent,
ContentChangedSchemaComponent,
ContentChangedTriggerComponent,
FormattableInputComponent,
GenericActionComponent,

10
frontend/src/app/features/rules/pages/simulator/rule-transition.component.ts

@ -5,8 +5,8 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { ChangeDetectionStrategy, Component, Input, OnChanges, SimpleChanges } from '@angular/core';
import { SimulatedRuleEventDto } from '@app/shared';
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { SimulatedRuleEventDto, TypedSimpleChanges } from '@app/shared';
@Component({
selector: 'sqx-rule-transition',
@ -14,7 +14,7 @@ import { SimulatedRuleEventDto } from '@app/shared';
templateUrl: './rule-transition.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class RuleTransitionComponent implements OnChanges {
export class RuleTransitionComponent {
@Input()
public event: SimulatedRuleEventDto | undefined | null;
@ -26,8 +26,8 @@ export class RuleTransitionComponent implements OnChanges {
public filteredErrors?: string[] | null;
public ngOnChanges(changes: SimpleChanges) {
if (changes['event'] || changes['errors']) {
public ngOnChanges(changes: TypedSimpleChanges<this>) {
if (changes.event || changes.errors) {
const errors = this.errors;
if (!errors) {

2
frontend/src/app/features/rules/shared/actions/formattable-input.component.html

@ -1,6 +1,6 @@
<ng-container *ngIf="type === 'Text'; else code">
<div class="input-group">
<input class="form-control" ngDefaultControl />
<sqx-code-editor [completion]="actualCompletion" [singleLine]="true" [mode]="aceMode"></sqx-code-editor>
<div class="input-group-apppend">
<select class="form-select" [ngModel]="mode" (ngModelChange)="setMode($event)" [disabled]="disabled" tabindex="-1">

4
frontend/src/app/features/rules/shared/actions/formattable-input.component.scss

@ -1,6 +1,10 @@
@import 'mixins';
@import 'vars';
sqx-code-editor {
flex-grow: 1;
}
.input-group-apppend {
z-index: 1;

2
frontend/src/app/features/rules/shared/actions/formattable-input.component.ts

@ -35,7 +35,7 @@ export class FormattableInputComponent implements ControlValueAccessor, AfterVie
public type: 'Text' | 'Code' = 'Text';
@Input()
public completion: ReadonlyArray<{ path: string; description: string }> | undefined | null;
public completion: ReadonlyArray<{ path: string; description: string; type: string }> | undefined | null;
@ViewChild(DefaultValueAccessor)
public inputEditor!: DefaultValueAccessor;

10
frontend/src/app/features/rules/shared/actions/generic-action.component.ts

@ -5,9 +5,9 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { ChangeDetectionStrategy, Component, Input, OnChanges, SimpleChanges } from '@angular/core';
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { EMPTY, Observable, shareReplay } from 'rxjs';
import { ActionForm, RuleCompletions, RulesService } from '@app/shared';
import { ActionForm, RuleCompletions, RulesService, TypedSimpleChanges } from '@app/shared';
@Component({
selector: 'sqx-generic-action[actionForm][appName][trigger][triggerType]',
@ -15,7 +15,7 @@ import { ActionForm, RuleCompletions, RulesService } from '@app/shared';
templateUrl: './generic-action.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class GenericActionComponent implements OnChanges {
export class GenericActionComponent {
@Input()
public actionForm!: ActionForm;
@ -35,8 +35,8 @@ export class GenericActionComponent implements OnChanges {
) {
}
public ngOnChanges(changes: SimpleChanges) {
if (changes['appName'] || changes['triggerType']) {
public ngOnChanges(changes: TypedSimpleChanges<this>) {
if (changes.appName || changes.triggerType) {
if (this.triggerType) {
this.ruleCompletions = this.rulesService.getCompletions(this.appName, this.triggerType).pipe(shareReplay(1));
} else {

40
frontend/src/app/features/rules/shared/triggers/completions-cache.ts

@ -0,0 +1,40 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Injectable } from '@angular/core';
import { EMPTY, map, Observable, shareReplay } from 'rxjs';
import { SchemaCompletions, SchemasService, SchemasState } from '@app/shared';
@Injectable()
export class CompletionsCache {
private readonly cache: { [schema: string]: Observable<SchemaCompletions> } = {};
constructor(
private readonly schemasService: SchemasService,
private readonly schemasState: SchemasState,
) {
}
public getCompletions(schema: string, skipFunctions: boolean) {
if (!schema) {
return EMPTY;
}
let result = this.cache[schema];
if (!result) {
result = this.schemasService.getContentTriggerCompletion(this.schemasState.appName, schema).pipe(shareReplay(1));
this.cache[schema] = result;
}
if (skipFunctions) {
result = result.pipe(map(x => x.filter(c => c.type !== 'Function')));
}
return result;
}
}

29
frontend/src/app/features/rules/shared/triggers/content-changed-schema.component.html

@ -0,0 +1,29 @@
<div class="row gx-2" [formGroup]="form">
<div class="col col-3">
<sqx-control-errors for="action"></sqx-control-errors>
<select class="form-select schemas-control" formControlName="schemaId">
<option *ngFor="let schema of schemas; trackBy: trackBySchema" [ngValue]="schema.id">{{schema.displayName}}</option>
</select>
</div>
<div class="col-auto">
<div class="label text-muted">{{ 'schemas.rules.when' | sqxTranslate }}</div>
</div>
<div class="col">
<sqx-control-errors for="condition"></sqx-control-errors>
<sqx-code-editor [singleLine]="true" formControlName="condition" [completion]="completions | async"></sqx-code-editor>
</div>
<div class="col-auto">
<button type="button" class="btn btn-text-danger" [disabled]="form.disabled"
(sqxConfirmClick)="remove.emit()"
confirmTitle="i18n:rules.deleteContentChangedSchemaTitle"
confirmText="i18n:rules.deleteContentChangedSchemaText"
confirmRememberKey="deleteContentChangedSchema">
<i class="icon-bin2"></i>
</button>
</div>
</div>

8
frontend/src/app/features/rules/shared/triggers/content-changed-schema.component.scss

@ -0,0 +1,8 @@
@import 'mixins';
@import 'vars';
.label {
font-size: $font-small;
font-weight: normal;
line-height: 2.4rem;
}

47
frontend/src/app/features/rules/shared/triggers/content-changed-schema.component.ts

@ -0,0 +1,47 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { FormGroup } from '@angular/forms';
import { EMPTY, Observable, switchMap } from 'rxjs';
import { SchemaCompletions, SchemaDto, TypedSimpleChanges, value$ } from '@app/shared';
import { CompletionsCache } from './completions-cache';
@Component({
selector: 'sqx-content-changed-schema[form][schemas]',
styleUrls: ['./content-changed-schema.component.scss'],
templateUrl: './content-changed-schema.component.html',
})
export class ContentChangedSchemaComponent {
@Input()
public schemas?: ReadonlyArray<SchemaDto> | null;
@Input()
public form!: FormGroup;
@Output()
public remove = new EventEmitter<any>();
public completions: Observable<SchemaCompletions> = EMPTY;
constructor(
private readonly completionsCache: CompletionsCache,
) {
}
public ngOnChanges(changes: TypedSimpleChanges<this>) {
if (changes.form) {
this.completions =
value$(this.form.controls['schemaId']).pipe(
switchMap(x => this.completionsCache.getCompletions(x, true)));
}
}
public trackBySchema(_index: number, schema: SchemaDto) {
return schema.id;
}
}

63
frontend/src/app/features/rules/shared/triggers/content-changed-trigger.component.html

@ -1,55 +1,28 @@
<ng-container *ngIf="!triggerForm.form.controls.handleAll.value">
<table class="table table-middle table-sm table-fixed table-borderless">
<colgroup>
<col style="width: 40%;">
<col style="width: 60%;">
<col style="width: 40px;">
</colgroup>
<div class="mb-2" *ngFor="let form of schemasForm.controls; let i = index">
<sqx-content-changed-schema [schemas]="schemas" [form]="$any(form)" (remove)="remove(i)"></sqx-content-changed-schema>
</div>
<tr>
<td>
<label>{{ 'common.schema' | sqxTranslate }}</label>
</td>
<td>
<label>{{ 'common.condition' | sqxTranslate }}</label>
</td>
<th></th>
</tr>
<div class="form-group row gx-2 align-items-center">
<div class="col-3">
<div class="form-control preview">{{ 'common.schema' | sqxTranslate }}</div>
</div>
<tr *ngFor="let triggerSchema of triggerSchemas">
<td>
<span class="truncate">{{triggerSchema.schema.displayName}}</span>
</td>
<td class="text-center">
<input class="form-control code" placeholder="{{ 'rules.conditionHint' | sqxTranslate }}"
[disabled]="triggerForm.form.disabled"
[ngModelOptions]="{ updateOn: 'blur' }"
[ngModel]="triggerSchema.condition"
(ngModelChange)="updateCondition(triggerSchema.schema, $event)">
</td>
<td class="text-center">
<button type="button" class="btn btn-text-secondary" (click)="removeSchema(triggerSchema)" [disabled]="triggerForm.form.disabled">
<i class="icon-close"></i>
</button>
</td>
</tr>
</table>
<div class="col-auto">
<div class="label text-muted">{{ 'schemas.rules.when' | sqxTranslate }}</div>
</div>
<div class="section" *ngIf="schemasToAdd.length > 0">
<form class="row g-2" (ngSubmit)="addSchema()">
<div class="col">
<select class="form-select schemas-control" [disabled]="triggerForm.form.disabled" [(ngModel)]="schemaToAdd" name="schema">
<option *ngFor="let schema of schemasToAdd; trackBy: trackBySchema" [ngValue]="schema">{{schema.displayName}}</option>
</select>
</div>
<div class="col">
<div class="form-control preview">{{ 'schemas.rules.condition' | sqxTranslate }}</div>
</div>
<div class="col-auto">
<button type="submit" class="btn btn-success" [disabled]="triggerForm.form.disabled">{{ 'rules.addSchema' | sqxTranslate }}</button>
</div>
</form>
<div class="col-auto">
<button type="button" class="btn btn-success" (click)="add()">
<i class="icon-add"></i>
</button>
</div>
</div>
<div class="help">
<h4>{{ 'common.conditions' | sqxTranslate }}</h4>

6
frontend/src/app/features/rules/shared/triggers/content-changed-trigger.component.scss

@ -9,4 +9,10 @@
.form-check {
margin-top: 1rem;
}
.label {
font-size: $font-small;
font-weight: normal;
line-height: 2.4rem;
}

82
frontend/src/app/features/rules/shared/triggers/content-changed-trigger.component.ts

@ -5,21 +5,19 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Component, Input, OnChanges } from '@angular/core';
import { SchemaDto, TriggerForm } from '@app/shared';
export interface TriggerSchemaForm {
schema: SchemaDto;
condition?: string;
}
import { Component, Input } from '@angular/core';
import { SchemaDto, TemplatedFormArray, TriggerForm } from '@app/shared';
import { CompletionsCache } from './completions-cache';
@Component({
selector: 'sqx-content-changed-trigger[trigger][triggerForm]',
styleUrls: ['./content-changed-trigger.component.scss'],
templateUrl: './content-changed-trigger.component.html',
providers: [
CompletionsCache,
],
})
export class ContentChangedTriggerComponent implements OnChanges {
export class ContentChangedTriggerComponent {
@Input()
public schemas?: ReadonlyArray<SchemaDto> | null;
@ -29,69 +27,15 @@ export class ContentChangedTriggerComponent implements OnChanges {
@Input()
public triggerForm!: TriggerForm;
public triggerSchemas: TriggerSchemaForm[] = [];
public schemaToAdd!: SchemaDto;
public schemasToAdd!: ReadonlyArray<SchemaDto>;
public get hasSchema() {
return !!this.schemaToAdd;
}
public ngOnChanges() {
const schemas: TriggerSchemaForm[] = [];
if (this.trigger.schemas && this.schemas) {
for (const triggerSchema of this.trigger.schemas) {
const schema = this.schemas.find(s => s.id === triggerSchema.schemaId);
if (schema) {
const condition = triggerSchema.condition;
schemas.push({ schema, condition });
}
}
}
this.triggerSchemas = schemas;
this.triggerSchemas.sortByString(x => x.schema.name);
this.updateSchemaToAdd();
}
public removeSchema(schemaForm: TriggerSchemaForm) {
this.triggerSchemas.remove(schemaForm);
this.updateValue();
this.updateSchemaToAdd();
}
public addSchema() {
this.triggerSchemas.push({ schema: this.schemaToAdd });
this.triggerSchemas.sortByString(x => x.schema.name);
this.updateValue();
this.updateSchemaToAdd();
}
public updateCondition(schema: SchemaDto, condition: string) {
this.triggerSchemas.replaceBy('schema', { schema, condition });
this.updateValue();
}
public updateValue() {
const schemas = this.triggerSchemas.map(({ schema, condition }) => ({ schemaId: schema.id, condition }));
this.triggerForm.form.patchValue({ schemas });
public get schemasForm() {
return this.triggerForm.form.get('schemas') as TemplatedFormArray;
}
private updateSchemaToAdd() {
this.schemasToAdd = this.schemas?.filter(schema => !this.triggerSchemas.find(s => s.schema.id === schema.id)).sortByString(x => x.name) || [];
this.schemaToAdd = this.schemasToAdd[0];
public add() {
this.schemasForm.add();
}
public trackBySchema(_index: number, schema: SchemaDto) {
return schema.id;
public remove(index: number) {
this.schemasForm.removeAt(index);
}
}

4
frontend/src/app/features/schemas/pages/schema/common/schema-edit-form.component.ts

@ -5,7 +5,7 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Component, Input, OnChanges } from '@angular/core';
import { Component, Input } from '@angular/core';
import { EditSchemaForm, SchemaDto, SchemasState } from '@app/shared';
@Component({
@ -13,7 +13,7 @@ import { EditSchemaForm, SchemaDto, SchemasState } from '@app/shared';
styleUrls: ['./schema-edit-form.component.scss'],
templateUrl: './schema-edit-form.component.html',
})
export class SchemaEditFormComponent implements OnChanges {
export class SchemaEditFormComponent {
@Input()
public schema!: SchemaDto;

4
frontend/src/app/features/schemas/pages/schema/export/schema-export-form.component.ts

@ -5,7 +5,7 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Component, Input, OnChanges } from '@angular/core';
import { Component, Input } from '@angular/core';
import { SchemaDto, SchemasState, SynchronizeSchemaForm } from '@app/shared';
@Component({
@ -13,7 +13,7 @@ import { SchemaDto, SchemasState, SynchronizeSchemaForm } from '@app/shared';
styleUrls: ['./schema-export-form.component.scss'],
templateUrl: './schema-export-form.component.html',
})
export class SchemaExportFormComponent implements OnChanges {
export class SchemaExportFormComponent {
@Input()
public schema!: SchemaDto;

2
frontend/src/app/features/schemas/pages/schema/fields/field-group.component.html

@ -3,7 +3,7 @@
<sqx-field [field]="$any(fieldGroup.separator)" [languages]="languages" [parent]="parent" [plain]="true" [schema]="schema" [settings]="settings">
<div class="d-flex align-items-center">
<i cdkDragHandle class="icon-drag2 drag-handle"></i>
<ng-content></ng-content>
<button type="button" class="btn btn-sm btn-text-secondary ms-2" (click)="toggle()">
<i [class.icon-caret-right]="snapshot.isCollapsed" [class.icon-caret-down]="!snapshot.isCollapsed"></i>

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

@ -5,15 +5,15 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
import { AppSettingsDto, createProperties, DialogModel, EditFieldForm, FieldDto, LanguageDto, ModalModel, NestedFieldDto, RootFieldDto, SchemaDto, SchemasState } from '@app/shared';
import { Component, Input } from '@angular/core';
import { AppSettingsDto, createProperties, DialogModel, EditFieldForm, FieldDto, LanguageDto, ModalModel, NestedFieldDto, RootFieldDto, SchemaDto, SchemasState, TypedSimpleChanges } from '@app/shared';
@Component({
selector: 'sqx-field[field][languages][schema][settings]',
styleUrls: ['./field.component.scss'],
templateUrl: './field.component.html',
})
export class FieldComponent implements OnChanges {
export class FieldComponent {
@Input()
public field!: NestedFieldDto | RootFieldDto;
@ -50,8 +50,8 @@ export class FieldComponent implements OnChanges {
) {
}
public ngOnChanges(changes: SimpleChanges) {
if (changes['field']) {
public ngOnChanges(changes: TypedSimpleChanges<this>) {
if (changes.field) {
this.isEditable = this.field.canUpdate;
this.editForm = new EditFieldForm(this.field.properties);

9
frontend/src/app/features/schemas/pages/schema/fields/sortable-field-list.component.html

@ -1,9 +1,13 @@
<div cdkDropListGroup
<div
cdkDropListGroup
cdkDropList
[cdkDropListDisabled]="!sortable"
[cdkDropListData]="fieldGroups"
(cdkDropListDropped)="sortGroups($event)">
<div *ngFor="let group of fieldGroups;" class="table-drag" cdkDrag cdkDragLockAxis="y">
<div *ngFor="let group of fieldGroups;" class="table-drag"
cdkDrag
cdkDragLockAxis="y"
[cdkDragDisabled]="!group.separator">
<sqx-field-group
[languages]="languages"
[fieldsEmpty]="fieldsEmpty"
@ -13,6 +17,7 @@
[settings]="settings"
[sortable]="sortable"
(sorted)="sortFields($event)">
<i cdkDragHandle class="icon-drag2 drag-handle"></i>
</sqx-field-group>
</div>
</div>

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

@ -5,17 +5,17 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
import { Component, Input } from '@angular/core';
import { UntypedFormGroup } from '@angular/forms';
import { Observable } from 'rxjs';
import { BooleanFieldPropertiesDto, FieldDto, hasNoValue$, LanguageDto } from '@app/shared';
import { BooleanFieldPropertiesDto, FieldDto, hasNoValue$, LanguageDto, TypedSimpleChanges } from '@app/shared';
@Component({
selector: 'sqx-boolean-validation[field][fieldForm][languages][properties]',
styleUrls: ['boolean-validation.component.scss'],
templateUrl: 'boolean-validation.component.html',
})
export class BooleanValidationComponent implements OnChanges {
export class BooleanValidationComponent {
@Input()
public fieldForm!: UntypedFormGroup;
@ -33,8 +33,8 @@ export class BooleanValidationComponent implements OnChanges {
public showDefaultValue?: Observable<boolean>;
public ngOnChanges(changes: SimpleChanges) {
if (changes['fieldForm']) {
public ngOnChanges(changes: TypedSimpleChanges<this>) {
if (changes.fieldForm) {
this.showDefaultValue =
hasNoValue$(this.fieldForm.controls['isRequired']);
}

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

@ -5,10 +5,10 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
import { Component, Input } from '@angular/core';
import { UntypedFormGroup } from '@angular/forms';
import { Observable } from 'rxjs';
import { DateTimeFieldPropertiesDto, FieldDto, hasNoValue$, LanguageDto } from '@app/shared';
import { DateTimeFieldPropertiesDto, FieldDto, hasNoValue$, LanguageDto, TypedSimpleChanges } from '@app/shared';
const CALCULATED_DEFAULT_VALUES: ReadonlyArray<string> = ['Now', 'Today'];
@ -17,7 +17,7 @@ const CALCULATED_DEFAULT_VALUES: ReadonlyArray<string> = ['Now', 'Today'];
styleUrls: ['date-time-validation.component.scss'],
templateUrl: 'date-time-validation.component.html',
})
export class DateTimeValidationComponent implements OnChanges {
export class DateTimeValidationComponent {
@Input()
public fieldForm!: UntypedFormGroup;
@ -38,8 +38,8 @@ export class DateTimeValidationComponent implements OnChanges {
public calculatedDefaultValues = CALCULATED_DEFAULT_VALUES;
public ngOnChanges(changes: SimpleChanges) {
if (changes['fieldForm']) {
public ngOnChanges(changes: TypedSimpleChanges<this>) {
if (changes.fieldForm) {
this.showDefaultValues =
hasNoValue$(this.fieldForm.controls['isRequired']);

10
frontend/src/app/features/schemas/pages/schema/fields/types/number-ui.component.ts

@ -5,17 +5,17 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
import { Component, Input } from '@angular/core';
import { UntypedFormGroup } from '@angular/forms';
import { Observable } from 'rxjs';
import { FieldDto, FloatConverter, NUMBER_FIELD_EDITORS, NumberFieldPropertiesDto, ResourceOwner, valueProjection$ } from '@app/shared';
import { FieldDto, FloatConverter, NUMBER_FIELD_EDITORS, NumberFieldPropertiesDto, ResourceOwner, valueProjection$, TypedSimpleChanges } from '@app/shared';
@Component({
selector: 'sqx-number-ui[field][fieldForm][properties]',
styleUrls: ['number-ui.component.scss'],
templateUrl: 'number-ui.component.html',
})
export class NumberUIComponent extends ResourceOwner implements OnChanges {
export class NumberUIComponent extends ResourceOwner {
public readonly converter = FloatConverter.INSTANCE;
public readonly editors = NUMBER_FIELD_EDITORS;
@ -31,8 +31,8 @@ export class NumberUIComponent extends ResourceOwner implements OnChanges {
public hideAllowedValues?: Observable<boolean>;
public hideInlineEditable?: Observable<boolean>;
public ngOnChanges(changes: SimpleChanges) {
if (changes['fieldForm']) {
public ngOnChanges(changes: TypedSimpleChanges<this>) {
if (changes.fieldForm) {
this.unsubscribeAll();
const editor = this.fieldForm.controls['editor'];

10
frontend/src/app/features/schemas/pages/schema/fields/types/string-ui.component.ts

@ -5,17 +5,17 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
import { Component, Input } from '@angular/core';
import { UntypedFormGroup } from '@angular/forms';
import { Observable } from 'rxjs';
import { FieldDto, ResourceOwner, SchemaTagSource, STRING_FIELD_EDITORS, StringFieldPropertiesDto, valueProjection$ } from '@app/shared';
import { FieldDto, ResourceOwner, SchemaTagSource, STRING_FIELD_EDITORS, StringFieldPropertiesDto, valueProjection$, TypedSimpleChanges } from '@app/shared';
@Component({
selector: 'sqx-string-ui[field][fieldForm][properties]',
styleUrls: ['string-ui.component.scss'],
templateUrl: 'string-ui.component.html',
})
export class StringUIComponent extends ResourceOwner implements OnChanges {
export class StringUIComponent extends ResourceOwner {
public readonly editors = STRING_FIELD_EDITORS;
@Input()
@ -37,8 +37,8 @@ export class StringUIComponent extends ResourceOwner implements OnChanges {
super();
}
public ngOnChanges(changes: SimpleChanges) {
if (changes['fieldForm']) {
public ngOnChanges(changes: TypedSimpleChanges<this>) {
if (changes.fieldForm) {
this.unsubscribeAll();
const editor = this.fieldForm.controls['editor'];

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

@ -5,17 +5,17 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
import { Component, Input } from '@angular/core';
import { UntypedFormGroup } from '@angular/forms';
import { Observable } from 'rxjs';
import { AppSettingsDto, FieldDto, hasNoValue$, hasValue$, LanguageDto, ModalModel, PatternDto, ResourceOwner, RootFieldDto, SchemaDto, STRING_CONTENT_TYPES, StringFieldPropertiesDto, Types, value$ } from '@app/shared';
import { AppSettingsDto, FieldDto, hasNoValue$, hasValue$, LanguageDto, ModalModel, PatternDto, ResourceOwner, RootFieldDto, SchemaDto, STRING_CONTENT_TYPES, StringFieldPropertiesDto, Types, value$, TypedSimpleChanges } from '@app/shared';
@Component({
selector: 'sqx-string-validation[field][fieldForm][languages][properties][schema][settings]',
styleUrls: ['string-validation.component.scss'],
templateUrl: 'string-validation.component.html',
})
export class StringValidationComponent extends ResourceOwner implements OnChanges {
export class StringValidationComponent extends ResourceOwner {
public readonly contentTypes = STRING_CONTENT_TYPES;
@Input()
@ -49,12 +49,12 @@ export class StringValidationComponent extends ResourceOwner implements OnChange
return Types.is(this.field, RootFieldDto) && !this.field.isLocalizable && this.schema.type !== 'Component';
}
public ngOnChanges(changes: SimpleChanges) {
if (changes['settings']) {
public ngOnChanges(changes: TypedSimpleChanges<this>) {
if (changes.settings) {
this.setPatternName();
}
if (changes['fieldForm']) {
if (changes.fieldForm) {
this.unsubscribeAll();
this.showPatternSuggestions =

2
frontend/src/app/features/schemas/pages/schema/preview/schema-preview-urls-form.component.html

@ -12,7 +12,7 @@
{{ 'schemas.previewUrls.empty' | sqxTranslate }}
</div>
<div class="form-group row gx-2" *ngFor="let form of editForm.previewControls; let i = index" [formGroup]="form">
<div class="mb-2 row gx-2" *ngFor="let form of editForm.previewControls; let i = index" [formGroup]="form">
<div class="col col-name">
<sqx-control-errors for="name"></sqx-control-errors>

4
frontend/src/app/features/schemas/pages/schema/preview/schema-preview-urls-form.component.ts

@ -5,7 +5,7 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Component, Input, OnChanges } from '@angular/core';
import { Component, Input } from '@angular/core';
import { ConfigurePreviewUrlsForm, SchemaDto, SchemasState } from '@app/shared';
@Component({
@ -13,7 +13,7 @@ import { ConfigurePreviewUrlsForm, SchemaDto, SchemasState } from '@app/shared';
styleUrls: ['./schema-preview-urls-form.component.scss'],
templateUrl: './schema-preview-urls-form.component.html',
})
export class SchemaPreviewUrlsFormComponent implements OnChanges {
export class SchemaPreviewUrlsFormComponent {
@Input()
public schema!: SchemaDto;

4
frontend/src/app/features/schemas/pages/schema/rules/schema-field-rules-form.component.html

@ -8,7 +8,7 @@
{{ 'schemas.rules.empty' | sqxTranslate }}
</div>
<div class="form-group row gx-2" *ngFor="let form of editForm.rulesControls; let i = index" [formGroup]="form">
<div class="mb-2 row gx-2" *ngFor="let form of editForm.rulesControls; let i = index" [formGroup]="form">
<div class="col col-action">
<sqx-control-errors for="action"></sqx-control-errors>
@ -32,7 +32,7 @@
<div class="col">
<sqx-control-errors for="condition"></sqx-control-errors>
<input class="form-control" formControlName="condition" placeholder="{{ 'schemas.rules.condition' | sqxTranslate }}">
<sqx-code-editor [singleLine]="true" formControlName="condition" [completion]="fieldCompletions | async"></sqx-code-editor>
</div>
<div class="col-auto">

13
frontend/src/app/features/schemas/pages/schema/rules/schema-field-rules-form.component.ts

@ -5,15 +5,16 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Component, Input, OnChanges } from '@angular/core';
import { ConfigureFieldRulesForm, FIELD_RULE_ACTIONS, SchemaDto, SchemasState } from '@app/shared';
import { Component, Input, OnInit } from '@angular/core';
import { EMPTY, Observable, shareReplay } from 'rxjs';
import { ConfigureFieldRulesForm, FIELD_RULE_ACTIONS, SchemaCompletions, SchemaDto, SchemasService, SchemasState } from '@app/shared';
@Component({
selector: 'sqx-schema-field-rules-form',
styleUrls: ['./schema-field-rules-form.component.scss'],
templateUrl: './schema-field-rules-form.component.html',
})
export class SchemaFieldRulesFormComponent implements OnChanges {
export class SchemaFieldRulesFormComponent implements OnInit {
@Input()
public schema!: SchemaDto;
@ -21,14 +22,20 @@ export class SchemaFieldRulesFormComponent implements OnChanges {
public fieldNames!: ReadonlyArray<string>;
public fieldActions = FIELD_RULE_ACTIONS;
public fieldCompletions: Observable<SchemaCompletions> = EMPTY;
public isEditable = false;
constructor(
private readonly schemasState: SchemasState,
private readonly schemasService: SchemasService,
) {
}
public ngOnInit() {
this.fieldCompletions = this.schemasService.getFieldRulesCompletion(this.schemasState.appName, this.schema.name).pipe(shareReplay(1));
}
public ngOnChanges() {
const fieldNames: string[] = [];

6
frontend/src/app/features/schemas/pages/schema/scripts/schema-scripts-form.component.ts

@ -5,7 +5,7 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Component, Input, OnChanges } from '@angular/core';
import { Component, Input } from '@angular/core';
import { EMPTY, Observable, shareReplay } from 'rxjs';
import { AppsState, EditSchemaScriptsForm, SchemaCompletions, SchemaDto, SchemasService, SchemasState } from '@app/shared';
@ -14,7 +14,7 @@ import { AppsState, EditSchemaScriptsForm, SchemaCompletions, SchemaDto, Schemas
styleUrls: ['./schema-scripts-form.component.scss'],
templateUrl: './schema-scripts-form.component.html',
})
export class SchemaScriptsFormComponent implements OnChanges {
export class SchemaScriptsFormComponent {
@Input()
public schema!: SchemaDto;
@ -38,7 +38,7 @@ export class SchemaScriptsFormComponent implements OnChanges {
this.editForm.load(this.schema.scripts);
this.editForm.setEnabled(this.isEditable);
this.schemaCompletions = this.schemasService.getCompletions(this.appsState.appName, this.schema.name).pipe(shareReplay(1));
this.schemaCompletions = this.schemasService.getContentScriptsCompletion(this.appsState.appName, this.schema.name).pipe(shareReplay(1));
}
public selectField(field: string) {

4
frontend/src/app/features/schemas/pages/schema/ui/field-list.component.ts

@ -6,7 +6,7 @@
*/
import { CdkDragDrop, moveItemInArray, transferArrayItem } from '@angular/cdk/drag-drop';
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Output } from '@angular/core';
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
import { META_FIELDS, SchemaDto, TableField } from '@app/shared';
const META_FIELD_NAMES = Object.values(META_FIELDS).filter(x => x !== META_FIELDS.empty);
@ -17,7 +17,7 @@ const META_FIELD_NAMES = Object.values(META_FIELDS).filter(x => x !== META_FIELD
templateUrl: './field-list.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class FieldListComponent implements OnChanges {
export class FieldListComponent {
@Input()
public emptyText = '';

4
frontend/src/app/features/schemas/pages/schema/ui/schema-ui-form.component.ts

@ -5,7 +5,7 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Component, Input, OnChanges } from '@angular/core';
import { Component, Input } from '@angular/core';
import { SchemaDto, SchemasState } from '@app/shared';
@Component({
@ -13,7 +13,7 @@ import { SchemaDto, SchemasState } from '@app/shared';
styleUrls: ['./schema-ui-form.component.scss'],
templateUrl: './schema-ui-form.component.html',
})
export class SchemaUIFormComponent implements OnChanges {
export class SchemaUIFormComponent {
@Input()
public schema!: SchemaDto;

10
frontend/src/app/features/settings/pages/backups/backup.component.ts

@ -5,8 +5,8 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { ChangeDetectionStrategy, Component, Input, OnChanges, SimpleChanges } from '@angular/core';
import { ApiUrlConfig, BackupDto, BackupsState, Duration } from '@app/shared';
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { ApiUrlConfig, BackupDto, BackupsState, Duration, TypedSimpleChanges } from '@app/shared';
@Component({
selector: 'sqx-backup[backup]',
@ -14,7 +14,7 @@ import { ApiUrlConfig, BackupDto, BackupsState, Duration } from '@app/shared';
templateUrl: './backup.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class BackupComponent implements OnChanges {
export class BackupComponent {
@Input()
public backup!: BackupDto;
@ -26,8 +26,8 @@ export class BackupComponent implements OnChanges {
) {
}
public ngOnChanges(changes: SimpleChanges) {
if (changes['backup']) {
public ngOnChanges(changes: TypedSimpleChanges<this>) {
if (changes.backup) {
this.duration = Duration.create(this.backup.started, this.backup.stopped!).toString();
}
}

10
frontend/src/app/features/settings/pages/clients/client.component.ts

@ -5,8 +5,8 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { ChangeDetectionStrategy, Component, Input, OnChanges, SimpleChanges } from '@angular/core';
import { AppsState, ClientDto, ClientsState, DialogModel, RoleDto } from '@app/shared';
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { AppsState, ClientDto, ClientsState, DialogModel, RoleDto, TypedSimpleChanges } from '@app/shared';
@Component({
selector: 'sqx-client[client][clientRoles]',
@ -14,7 +14,7 @@ import { AppsState, ClientDto, ClientsState, DialogModel, RoleDto } from '@app/s
templateUrl: './client.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ClientComponent implements OnChanges {
export class ClientComponent {
@Input()
public client!: ClientDto;
@ -31,8 +31,8 @@ export class ClientComponent implements OnChanges {
) {
}
public ngOnChanges(changes: SimpleChanges) {
if (changes['client']) {
public ngOnChanges(changes: TypedSimpleChanges<this>) {
if (changes.client) {
this.apiCallsLimit = this.client.apiCallsLimit;
}
}

4
frontend/src/app/features/settings/pages/contributors/contributor-add-form.component.ts

@ -5,7 +5,7 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Component, Injectable, Input, OnChanges } from '@angular/core';
import { Component, Injectable, Input } from '@angular/core';
import { Observable, of } from 'rxjs';
import { withLatestFrom } from 'rxjs/operators';
import { AssignContributorForm, AutocompleteSource, ContributorsState, DialogModel, DialogService, RoleDto, UsersService } from '@app/shared';
@ -45,7 +45,7 @@ export class UsersDataSource implements AutocompleteSource {
UsersDataSource,
],
})
export class ContributorAddFormComponent implements OnChanges {
export class ContributorAddFormComponent {
private defaultValue: any;
@Input()

4
frontend/src/app/features/settings/pages/languages/language.component.ts

@ -6,7 +6,7 @@
*/
import { CdkDragDrop } from '@angular/cdk/drag-drop';
import { Component, Input, OnChanges } from '@angular/core';
import { Component, Input } from '@angular/core';
import { AppLanguageDto, EditLanguageForm, LanguageDto, LanguagesState, sorted } from '@app/shared';
@Component({
@ -14,7 +14,7 @@ import { AppLanguageDto, EditLanguageForm, LanguageDto, LanguagesState, sorted }
styleUrls: ['./language.component.scss'],
templateUrl: './language.component.html',
})
export class LanguageComponent implements OnChanges {
export class LanguageComponent {
@Input()
public language!: AppLanguageDto;

10
frontend/src/app/features/settings/pages/roles/role.component.ts

@ -5,8 +5,8 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { ChangeDetectionStrategy, Component, Input, OnChanges, SimpleChanges, ViewChild } from '@angular/core';
import { AutocompleteComponent, AutocompleteSource, EditRoleForm, RoleDto, RolesState, SchemaDto, Settings } from '@app/shared';
import { ChangeDetectionStrategy, Component, Input, ViewChild } from '@angular/core';
import { AutocompleteComponent, AutocompleteSource, EditRoleForm, RoleDto, RolesState, SchemaDto, Settings, TypedSimpleChanges } from '@app/shared';
const DESCRIPTIONS = {
Developer: 'i18n:roles.defaults.developer',
@ -37,7 +37,7 @@ const SIMPLE_PROPERTIES: ReadonlyArray<Property> = [{
templateUrl: './role.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class RoleComponent implements OnChanges {
export class RoleComponent {
@Input()
public role!: RoleDto;
@ -70,8 +70,8 @@ export class RoleComponent implements OnChanges {
) {
}
public ngOnChanges(changes: SimpleChanges) {
if (changes['role']) {
public ngOnChanges(changes: TypedSimpleChanges<this>) {
if (changes.role) {
this.isEditable = this.role.canUpdate;
this.properties = this.role.properties;

4
frontend/src/app/features/settings/pages/templates/template.component.ts

@ -5,7 +5,7 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { ChangeDetectionStrategy, Component, Input, OnChanges } from '@angular/core';
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { map, Observable, shareReplay } from 'rxjs';
import { AppsState, ClientsState, TemplateDetailsDto, TemplateDto, TemplatesService } from '@app/shared';
@ -15,7 +15,7 @@ import { AppsState, ClientsState, TemplateDetailsDto, TemplateDto, TemplatesServ
templateUrl: './template.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class TemplateComponent implements OnChanges {
export class TemplateComponent {
@Input()
public template!: TemplateDto;

4
frontend/src/app/features/settings/pages/workflows/workflow-diagram.component.ts

@ -5,7 +5,7 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { AfterViewInit, Component, ElementRef, Input, OnChanges, OnDestroy, ViewChild } from '@angular/core';
import { AfterViewInit, Component, ElementRef, Input, OnDestroy, ViewChild } from '@angular/core';
import { ResourceLoaderService, WorkflowDto } from '@app/shared';
declare const vis: any;
@ -15,7 +15,7 @@ declare const vis: any;
styleUrls: ['./workflow-diagram.component.scss'],
templateUrl: './workflow-diagram.component.html',
})
export class WorkflowDiagramComponent implements AfterViewInit, OnDestroy, OnChanges {
export class WorkflowDiagramComponent implements AfterViewInit, OnDestroy {
private network: any;
@ViewChild('chartContainer', { static: false })

10
frontend/src/app/features/settings/pages/workflows/workflow-step.component.ts

@ -5,15 +5,15 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core';
import { WorkflowDto, WorkflowStep, WorkflowStepValues, WorkflowTransition, WorkflowTransitionValues, WorkflowTransitionView } from '@app/shared';
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { TypedSimpleChanges, WorkflowDto, WorkflowStep, WorkflowStepValues, WorkflowTransition, WorkflowTransitionValues, WorkflowTransitionView } from '@app/shared';
@Component({
selector: 'sqx-workflow-step[roles][step][workflow]',
styleUrls: ['./workflow-step.component.scss'],
templateUrl: './workflow-step.component.html',
})
export class WorkflowStepComponent implements OnChanges {
export class WorkflowStepComponent {
public readonly onBlur: { updateOn: 'blur' } = { updateOn: 'blur' };
@Output()
@ -54,8 +54,8 @@ export class WorkflowStepComponent implements OnChanges {
public transitions!: ReadonlyArray<WorkflowTransitionView>;
public ngOnChanges(changes: SimpleChanges) {
if (changes['workflow'] || changes['step']) {
public ngOnChanges(changes: TypedSimpleChanges<this>) {
if (changes.workflow || changes.step) {
this.openSteps = this.workflow.getOpenSteps(this.step);
this.openStep = this.openSteps[0];

4
frontend/src/app/features/settings/pages/workflows/workflow.component.ts

@ -5,7 +5,7 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Component, Input, OnChanges } from '@angular/core';
import { Component, Input } from '@angular/core';
import { ErrorDto, MathHelper, SchemaTagSource, WorkflowDto, WorkflowsState, WorkflowStep, WorkflowStepValues, WorkflowTransition, WorkflowTransitionValues } from '@app/shared';
@Component({
@ -13,7 +13,7 @@ import { ErrorDto, MathHelper, SchemaTagSource, WorkflowDto, WorkflowsState, Wor
styleUrls: ['./workflow.component.scss'],
templateUrl: './workflow.component.html',
})
export class WorkflowComponent implements OnChanges {
export class WorkflowComponent {
public readonly onBlur: { updateOn: 'blur' } = { updateOn: 'blur' };
@Input()

13
frontend/src/app/framework/angular/avatar.component.ts

@ -5,8 +5,9 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { ChangeDetectionStrategy, Component, Input, OnChanges, SimpleChanges } from '@angular/core';
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { picasso } from '@app/framework/internal';
import { TypedSimpleChanges } from './helpers';
@Component({
selector: 'sqx-avatar',
@ -14,7 +15,7 @@ import { picasso } from '@app/framework/internal';
templateUrl: './avatar.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AvatarComponent implements OnChanges {
export class AvatarComponent {
@Input()
public identifier: string | undefined | null;
@ -29,16 +30,16 @@ export class AvatarComponent implements OnChanges {
public actualImage: string | undefined | null;
public ngOnChanges(changes: SimpleChanges) {
if (changes['image']) {
public ngOnChanges(changes: TypedSimpleChanges<this>) {
if (changes.image) {
this.actualImage = this.image;
}
if (changes['image'] || changes['identifier']) {
if (changes.image || changes.identifier) {
this.updateSource();
}
if (changes['size']) {
if (changes.size) {
this.imageSize = `${this.size}px`;
}
}

4
frontend/src/app/framework/angular/forms/control-errors.component.ts

@ -5,7 +5,7 @@
* Copyright (c) Sebastian Stehle. All rights r vbeserved
*/
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Host, Input, OnChanges, OnDestroy, Optional } from '@angular/core';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Host, Input, OnDestroy, Optional } from '@angular/core';
import { AbstractControl, FormGroupDirective, UntypedFormArray } from '@angular/forms';
import { merge } from 'rxjs';
import { LocalizerService, StatefulComponent, Types } from '@app/framework/internal';
@ -23,7 +23,7 @@ interface State {
templateUrl: './control-errors.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ControlErrorsComponent extends StatefulComponent<State> implements OnChanges, OnDestroy {
export class ControlErrorsComponent extends StatefulComponent<State> implements OnDestroy {
private controlDisplayName = '';
private control: AbstractControl | null = null;

4
frontend/src/app/framework/angular/forms/editors/checkbox-group.component.ts

@ -5,7 +5,7 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { AfterViewChecked, AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, forwardRef, Input, OnChanges, ViewChild } from '@angular/core';
import { AfterViewChecked, AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, forwardRef, Input, ViewChild } from '@angular/core';
import { NG_VALUE_ACCESSOR } from '@angular/forms';
import { getTagValues, MathHelper, StatefulControlComponent, TagValue, TextMeasurer, Types } from '@app/framework/internal';
@ -30,7 +30,7 @@ interface State {
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CheckboxGroupComponent extends StatefulControlComponent<State, string[]> implements AfterViewInit, AfterViewChecked, OnChanges {
export class CheckboxGroupComponent extends StatefulControlComponent<State, string[]> implements AfterViewInit, AfterViewChecked {
private readonly textMeasurer: TextMeasurer;
private childrenWidth = 0;
private checkedValuesRaw: any;

5
frontend/src/app/framework/angular/forms/editors/code-editor.component.html

@ -1 +1,4 @@
<div class="editor" [class.no-border]="borderless" #editor></div>
<div [class.form-control]="singleLine">
<span></span>
<div class="editor" [class.no-border]="borderless || singleLine" #editor></div>
</div>

23
frontend/src/app/framework/angular/forms/editors/code-editor.component.scss

@ -32,4 +32,27 @@
.ace_gutter {
background: $color-border !important;
}
.ace_scroller {
box-shadow: none !important;
}
.form-control {
display: flex;
flex-direction: row;
flex-grow: 1;
align-items: center;
.editor {
flex-grow: 1;
}
span {
height: 1.5rem;
}
.ace_hidden-cursors {
display: none;
}
}
}

136
frontend/src/app/framework/angular/forms/editors/code-editor.component.ts

@ -5,10 +5,11 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, forwardRef, Input, OnChanges, SimpleChanges, ViewChild } from '@angular/core';
import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, forwardRef, Input, ViewChild } from '@angular/core';
import { NG_VALUE_ACCESSOR } from '@angular/forms';
import { debounceTime, Subject } from 'rxjs';
import { ResourceLoaderService, StatefulControlComponent, Types } from '@app/framework/internal';
import { TypedSimpleChanges } from './../../helpers';
import { FocusComponent } from './../forms-helper';
declare const ace: any;
@ -26,8 +27,9 @@ export const SQX_CODE_EDITOR_CONTROL_VALUE_ACCESSOR: any = {
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CodeEditorComponent extends StatefulControlComponent<{}, string> implements AfterViewInit, FocusComponent, OnChanges {
export class CodeEditorComponent extends StatefulControlComponent<{}, string> implements AfterViewInit, FocusComponent {
private aceEditor: any;
private aceTools: any;
private valueChanged = new Subject();
private value = '';
private modelist: any;
@ -51,6 +53,9 @@ export class CodeEditorComponent extends StatefulControlComponent<{}, string> im
@Input()
public maxLines: number | undefined;
@Input()
public singleLine = false;
@Input()
public wordWrap = false;
@ -63,9 +68,9 @@ export class CodeEditorComponent extends StatefulControlComponent<{}, string> im
}
@Input()
public set completion(value: ReadonlyArray<{ path: string; description: string }> | undefined | null) {
public set completion(value: ReadonlyArray<{ path: string; description: string; type: string; allowedValues?: string[] }> | undefined | null) {
if (value) {
this.completions = value.map(({ path, description }) => ({ value: path, name: path, meta: 'context', description }));
this.completions = value.map(({ path, description, type, allowedValues }) => ({ value: path, name: path, description, meta: type?.toLowerCase(), path, allowedValues }));
} else {
this.completions = [];
}
@ -77,16 +82,16 @@ export class CodeEditorComponent extends StatefulControlComponent<{}, string> im
super(changeDetector, {});
}
public ngOnChanges(changes: SimpleChanges) {
if (changes['valueFile'] || changes['mode']) {
public ngOnChanges(changes: TypedSimpleChanges<this>) {
if (changes.valueFile || changes.mode) {
this.setMode();
}
if (changes['height'] || changes['maxLines']) {
this.setHeight();
if (changes.height || changes.maxLines || changes.singleLine) {
this.setOptions();
}
if (changes['wordWrap']) {
if (changes.wordWrap) {
this.setWordWrap();
}
}
@ -114,15 +119,19 @@ export class CodeEditorComponent extends StatefulControlComponent<{}, string> im
}
public onDisabled(isDisabled: boolean) {
if (this.aceEditor) {
this.aceEditor.setReadOnly(isDisabled);
if (!this.aceEditor) {
return;
}
this.aceEditor.setReadOnly(isDisabled);
}
public focus() {
if (this.aceEditor) {
this.aceEditor.focus();
if (!this.aceEditor) {
return;
}
this.aceEditor.focus();
}
public ngAfterViewInit() {
@ -136,39 +145,39 @@ export class CodeEditorComponent extends StatefulControlComponent<{}, string> im
this.resourceLoader.loadLocalScript('dependencies/ace/ext/modelist.js'),
this.resourceLoader.loadLocalScript('dependencies/ace/ext/language_tools.js'),
]).then(() => {
this.aceEditor = ace.edit(this.editor.nativeElement);
this.modelist = ace.require('ace/ext/modelist');
this.aceEditor.setReadOnly(this.snapshot.isDisabled);
this.aceEditor = ace.edit(this.editor.nativeElement);
this.aceEditor.setFontSize(14);
this.onDisabled(this.snapshot.isDisabled);
this.aceTools = ace.require('ace/ext/language_tools');
this.setValue(this.value);
this.setMode();
this.setHeight();
this.setOptions();
this.setWordWrap();
this.onDisabled(this.snapshot.isDisabled);
const langTools = ace.require('ace/ext/language_tools');
if (langTools) {
this.aceEditor.setOptions({
enableBasicAutocompletion: true,
enableSnippets: true,
enableLiveAutocompletion: true,
});
if (this.aceTools) {
const previous = this.aceEditor.completers;
this.aceEditor.completers = [
previous[0], {
previous?.[0], {
getCompletions: (editor: any, session: any, pos: any, prefix: any, callback: any) => {
callback(null, this.completions);
},
getDocTooltip: (item: any) => {
if (item.meta === 'context' && item.description) {
if (item.path && item.description) {
item.docHTML = `<b>${item.value}</b><hr></hr>${item.description}`;
if (item.allowedValues) {
item.docHTML += '<div class="mt-2 mb-2">Allowed Values:<ul>';
for (const value of item.allowedValues) {
item.docHTML += `<li><code>${value}</code></li>`;
}
item.docHTML += '</ul></div>';
}
}
},
// eslint-disable-next-line no-useless-escape
@ -176,7 +185,6 @@ export class CodeEditorComponent extends StatefulControlComponent<{}, string> im
},
];
}
this.aceEditor.on('blur', () => {
this.changeValue();
@ -187,6 +195,12 @@ export class CodeEditorComponent extends StatefulControlComponent<{}, string> im
this.valueChanged.next(true);
});
this.aceEditor.on('paste', (event: any) => {
if (this.singleLine) {
event.text = event.text.replace(/[\r\n]+/g, ' ');
}
});
this.detach();
});
}
@ -219,39 +233,61 @@ export class CodeEditorComponent extends StatefulControlComponent<{}, string> im
}
private setWordWrap() {
if (this.aceEditor) {
this.aceEditor.getSession().setUseWrapMode(this.wordWrap);
if (!this.aceEditor || this.singleLine) {
return;
}
this.aceEditor.getSession().setUseWrapMode(this.wordWrap);
}
private setMode() {
if (this.aceEditor) {
if (this.valueFile && this.modelist) {
const mode = this.modelist.getModeForPath(this.valueFile).mode;
if (!this.aceEditor) {
return;
}
this.aceEditor.getSession().setMode(mode);
} else {
this.aceEditor.getSession().setMode(this.mode);
}
if (this.valueFile && this.modelist) {
const mode = this.modelist.getModeForPath(this.valueFile).mode;
this.aceEditor.getSession().setMode(mode);
} else {
this.aceEditor.getSession().setMode(this.mode);
}
}
private setHeight() {
if (this.aceEditor && this.editor?.nativeElement) {
private setOptions() {
if (!this.aceEditor) {
return;
}
let maxLines = undefined;
let minLines = undefined;
if (!this.singleLine) {
if (Types.isNumber(this.height)) {
const lines = this.height / 15;
this.aceEditor.setOptions({
minLines: lines,
maxLines: lines,
});
maxLines = lines;
minLines = lines;
} else if (this.height === 'auto') {
this.aceEditor.setOptions({
minLines: 3,
maxLines: this.maxLines || 500,
});
maxLines = this.maxLines || 500;
minLines = Math.min(3, maxLines);
}
} else {
maxLines = 1;
minLines = 1;
}
this.aceEditor.setOptions({
autoScrollEditorIntoView: this.singleLine,
enableBasicAutocompletion: !!this.aceTools,
enableLiveAutocompletion: !!this.aceTools,
enableSnippets: !!this.aceTools && !this.singleLine,
highlightActiveLine: !this.singleLine,
maxLines,
minLines,
printMargin: !this.singleLine,
showGutter: !this.singleLine,
});
}
private setValue(value: string) {

99
frontend/src/app/framework/angular/forms/editors/code-editor.stories.ts

@ -0,0 +1,99 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { moduleMetadata } from '@storybook/angular';
import { Meta, Story } from '@storybook/angular/types-6-0';
import { CodeEditorComponent, SqxFrameworkModule } from '@app/framework';
export default {
title: 'Framework/CodeEditor',
component: CodeEditorComponent,
argTypes: {
disabled: {
control: 'boolean',
},
dropdownFullWidth: {
control: 'boolean',
},
},
decorators: [
moduleMetadata({
imports: [
BrowserAnimationsModule,
SqxFrameworkModule,
SqxFrameworkModule.forRoot(),
],
}),
],
} as Meta;
const Template: Story<CodeEditorComponent & { model: any }> = (args: CodeEditorComponent) => ({
props: args,
template: `
<div>
<sqx-code-editor
[borderless]="borderless"
[disabled]="disabled"
[height]="height"
[maxLines]="maxLines"
[singleLine]="singleLine"
[valueFile]="valueFile"
[valueMode]="valueMode"
[wordWrap]="wordWrap">
</sqx-code-editor>
</div>
`,
});
const SingleLineTemplate: Story<CodeEditorComponent & { model: any }> = (args: CodeEditorComponent) => ({
props: args,
template: `
<div class="row">
<div class="col">
<sqx-code-editor
[borderless]="borderless"
[completion]="completion"
[disabled]="disabled"
[height]="height"
[maxLines]="maxLines"
[singleLine]="singleLine"
[valueFile]="valueFile"
[valueMode]="valueMode"
[wordWrap]="wordWrap">
</sqx-code-editor>
</div>
<div class="col">
<input class="form-control">
</div>
</div>
`,
});
const COMPLETIONS = [{
path: 'path1',
description: 'Test1 Path',
}, {
path: 'path2',
description: 'Test2 Path',
}, {
path: 'path3',
description: 'Test3 Path',
}];
export const Default = Template.bind({});
Default.args = {
height: 'auto',
};
export const SingleLine = SingleLineTemplate.bind({});
SingleLine.args = {
completion: COMPLETIONS,
singleLine: true,
};

9
frontend/src/app/framework/angular/forms/editors/dropdown.component.ts

@ -5,10 +5,11 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { AfterContentInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChildren, EventEmitter, forwardRef, Input, OnChanges, OnInit, Output, QueryList, SimpleChanges, TemplateRef } from '@angular/core';
import { AfterContentInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChildren, EventEmitter, forwardRef, Input, OnInit, Output, QueryList, TemplateRef } from '@angular/core';
import { NG_VALUE_ACCESSOR, UntypedFormControl } from '@angular/forms';
import { map } from 'rxjs/operators';
import { Keys, ModalModel, RelativePosition, StatefulControlComponent, Types } from '@app/framework/internal';
import { TypedSimpleChanges } from './../../helpers';
export const SQX_DROPDOWN_CONTROL_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => DropdownComponent), multi: true,
@ -37,7 +38,7 @@ interface State {
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class DropdownComponent extends StatefulControlComponent<State, ReadonlyArray<any>> implements AfterContentInit, OnChanges, OnInit {
export class DropdownComponent extends StatefulControlComponent<State, ReadonlyArray<any>> implements AfterContentInit, OnInit {
private value: any;
@Output()
@ -127,8 +128,8 @@ export class DropdownComponent extends StatefulControlComponent<State, ReadonlyA
}));
}
public ngOnChanges(changes: SimpleChanges) {
if (changes['items']) {
public ngOnChanges(changes: TypedSimpleChanges<this>) {
if (changes.items) {
this.items = this.items || [];
this.next({ suggestedItems: this.items });

4
frontend/src/app/framework/angular/forms/editors/radio-group.component.ts

@ -5,7 +5,7 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { AfterViewChecked, AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, forwardRef, Input, OnChanges, ViewChild } from '@angular/core';
import { AfterViewChecked, AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, forwardRef, Input, ViewChild } from '@angular/core';
import { NG_VALUE_ACCESSOR } from '@angular/forms';
import { getTagValues, MathHelper, StatefulControlComponent, TagValue, TextMeasurer } from '@app/framework/internal';
@ -27,7 +27,7 @@ interface State {
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class RadioGroupComponent extends StatefulControlComponent<State, string> implements AfterViewInit, AfterViewChecked, OnChanges {
export class RadioGroupComponent extends StatefulControlComponent<State, string> implements AfterViewInit, AfterViewChecked {
private readonly textMeasurer: TextMeasurer;
private childrenWidth = 0;
private containerWidth = 0;

8
frontend/src/app/framework/angular/forms/editors/tag-editor.component.html

@ -27,7 +27,7 @@
<i class="icon-caret-down"></i>
</div>
<ng-container *sqxModal="snapshot.itemsList.length > 0">
<ng-container *sqxModal="snapshot.suggestedItems.length > 0">
<sqx-dropdown-menu class="control-dropdown"
[sqxAnchoredTo]="form"
[adjustWidth]="true"
@ -35,12 +35,12 @@
[scrollY]="true"
[style.minWidth]="dropdownWidth"
position="bottom-left" #container>
<div *ngFor="let item of snapshot.itemsList; let i = index" class="control-dropdown-item control-dropdown-item-selectable"
[class.active]="i === snapshot.itemsIndex"
<div *ngFor="let item of snapshot.suggestedItems; let i = index" class="control-dropdown-item control-dropdown-item-selectable"
[class.active]="i === snapshot.suggestedIndex"
[class.separated]="itemSeparator"
(mousedown)="selectValue(item)"
(mouseover)="selectIndex(i)"
[sqxScrollActive]="i === snapshot.itemsIndex"
[sqxScrollActive]="i === snapshot.suggestedIndex"
[sqxScrollContainer]="container.nativeElement">
<ng-container>{{item}}</ng-container>
</div>

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

@ -5,10 +5,11 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, EventEmitter, forwardRef, Input, OnChanges, OnInit, Output, SimpleChanges, ViewChild } from '@angular/core';
import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, EventEmitter, forwardRef, Input, OnInit, Output, ViewChild } from '@angular/core';
import { NG_VALUE_ACCESSOR, UntypedFormControl } from '@angular/forms';
import { distinctUntilChanged, map, tap } from 'rxjs/operators';
import { getTagValues, Keys, ModalModel, StatefulControlComponent, StringConverter, TagValue, TextMeasurer, Types } from '@app/framework/internal';
import { TypedSimpleChanges } from './../../helpers';
export const SQX_TAG_EDITOR_CONTROL_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => TagEditorComponent), multi: true,
@ -18,11 +19,11 @@ interface State {
// True, when the item has the focus.
hasFocus: boolean;
// The suggested item.
itemsList: ReadonlyArray<TagValue>;
// The suggested items.
suggestedItems: ReadonlyArray<any>;
// The index of the selected suggested items.
itemsIndex: number;
// The selected suggest item index.
suggestedIndex: number;
// All available tag values.
tags: ReadonlyArray<TagValue>;
@ -37,7 +38,7 @@ interface State {
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class TagEditorComponent extends StatefulControlComponent<State, ReadonlyArray<any>> implements AfterViewInit, OnChanges, OnInit {
export class TagEditorComponent extends StatefulControlComponent<State, ReadonlyArray<any>> implements AfterViewInit, OnInit {
private readonly textMeasurer: TextMeasurer;
private latestValue: any;
private latestInput?: string;
@ -114,8 +115,8 @@ export class TagEditorComponent extends StatefulControlComponent<State, Readonly
const items = this.itemsSorted.filter(s => s.lowerCaseName.includes(query) && !this.snapshot.tags.find(x => x.id === s.id));
this.next({
itemsIndex: -1,
itemsList: items || [],
suggestedIndex: -1,
suggestedItems: items || [],
});
}
}
@ -128,8 +129,8 @@ export class TagEditorComponent extends StatefulControlComponent<State, Readonly
constructor(changeDetector: ChangeDetectorRef) {
super(changeDetector, {
hasFocus: false,
itemsList: [],
itemsIndex: 0,
suggestedIndex: 0,
suggestedItems: [],
tags: [],
});
@ -140,8 +141,8 @@ export class TagEditorComponent extends StatefulControlComponent<State, Readonly
this.resetSize();
}
public ngOnChanges(changes: SimpleChanges) {
if (changes['converter']) {
public ngOnChanges(changes: TypedSimpleChanges<this>) {
if (changes.itemConverter) {
this.writeValue(this.latestValue, true);
}
}
@ -180,8 +181,8 @@ export class TagEditorComponent extends StatefulControlComponent<State, Readonly
}))
.subscribe(suggestedItems => {
this.next({
itemsIndex: -1,
itemsList: suggestedItems,
suggestedIndex: -1,
suggestedItems,
});
}));
}
@ -194,23 +195,23 @@ export class TagEditorComponent extends StatefulControlComponent<State, Readonly
this.resetSize();
}
const items: any[] = [];
const tags: any[] = [];
if (this.itemConverter && Types.isArray(obj)) {
for (const value of obj) {
if (Types.is(value, TagValue)) {
items.push(value);
tags.push(value);
} else {
const converted = this.itemConverter.convertValue(value);
if (converted) {
items.push(converted);
tags.push(converted);
}
}
}
}
this.next({ tags: items });
this.next({ tags });
}
public onDisabled(isDisabled: boolean) {
@ -286,8 +287,8 @@ export class TagEditorComponent extends StatefulControlComponent<State, Readonly
this.selectNextIndex();
return false;
} else if (Keys.isEnter(event)) {
if (this.snapshot.itemsIndex >= 0) {
if (this.selectValue(this.snapshot.itemsList[this.snapshot.itemsIndex])) {
if (this.snapshot.suggestedIndex >= 0) {
if (this.selectValue(this.snapshot.suggestedItems[this.snapshot.suggestedIndex])) {
return false;
}
} else if (this.acceptEnter) {
@ -335,11 +336,11 @@ export class TagEditorComponent extends StatefulControlComponent<State, Readonly
}
public selectPrevIndex() {
this.selectIndex(this.snapshot.itemsIndex - 1);
this.selectIndex(this.snapshot.suggestedIndex - 1);
}
public selectNextIndex() {
this.selectIndex(this.snapshot.itemsIndex + 1);
this.selectIndex(this.snapshot.suggestedIndex + 1);
}
public selectIndex(suggestedIndex: number) {
@ -347,11 +348,11 @@ export class TagEditorComponent extends StatefulControlComponent<State, Readonly
suggestedIndex = 0;
}
if (suggestedIndex >= this.snapshot.itemsList.length) {
suggestedIndex = this.snapshot.itemsList.length - 1;
if (suggestedIndex >= this.snapshot.suggestedItems.length) {
suggestedIndex = this.snapshot.suggestedItems.length - 1;
}
this.next({ itemsIndex: suggestedIndex });
this.next({ suggestedIndex });
}
public resetFocus(): any {
@ -359,7 +360,7 @@ export class TagEditorComponent extends StatefulControlComponent<State, Readonly
}
private resetAutocompletion() {
this.next({ itemsList: [], itemsIndex: -1 });
this.next({ suggestedItems: [], suggestedIndex: -1 });
}
private resetForm() {
@ -447,13 +448,13 @@ export class TagEditorComponent extends StatefulControlComponent<State, Readonly
return s && e && (e - s) > 0;
}
private updateItems(items: ReadonlyArray<TagValue>, touched: boolean) {
this.next({ tags: items });
private updateItems(tags: ReadonlyArray<TagValue>, touched: boolean) {
this.next({ tags });
if (items.length === 0 && this.undefinedWhenEmpty) {
if (tags.length === 0 && this.undefinedWhenEmpty) {
this.callChange(undefined);
} else {
this.callChange(items.map(x => x.value));
this.callChange(tags.map(x => x.value));
}
if (touched) {

9
frontend/src/app/framework/angular/forms/form-error.component.ts

@ -5,8 +5,9 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { ChangeDetectionStrategy, Component, Input, OnChanges, SimpleChanges } from '@angular/core';
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { ErrorDto } from '@app/framework/internal';
import { TypedSimpleChanges } from './../helpers';
@Component({
selector: 'sqx-form-error[error]',
@ -14,7 +15,7 @@ import { ErrorDto } from '@app/framework/internal';
templateUrl: './form-error.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class FormErrorComponent implements OnChanges {
export class FormErrorComponent {
@Input()
public error?: ErrorDto | null;
@ -26,8 +27,8 @@ export class FormErrorComponent implements OnChanges {
public show = false;
public ngOnChanges(changes: SimpleChanges) {
if (changes['error']) {
public ngOnChanges(changes: TypedSimpleChanges<this>) {
if (changes.error) {
this.show = !!this.error;
}
}

7
frontend/src/app/framework/angular/forms/progress-bar.component.ts

@ -5,8 +5,9 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, Input, OnChanges, OnInit, Renderer2, SimpleChanges } from '@angular/core';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, Input, OnInit, Renderer2 } from '@angular/core';
import * as ProgressBar from 'progressbar.js';
import { TypedSimpleChanges } from './../helpers';
@Component({
selector: 'sqx-progress-bar',
@ -18,7 +19,7 @@ import * as ProgressBar from 'progressbar.js';
template: '',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ProgressBarComponent implements OnChanges, OnInit {
export class ProgressBarComponent implements OnInit {
private progressBar: any;
@Input()
@ -72,7 +73,7 @@ export class ProgressBarComponent implements OnChanges, OnInit {
this.updateValue();
}
public ngOnChanges(changes: SimpleChanges) {
public ngOnChanges(changes: TypedSimpleChanges<this>) {
if (this.progressBar && changes.value) {
this.updateValue();
}

15
frontend/src/app/framework/angular/helpers.ts

@ -0,0 +1,15 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { SimpleChange } from '@angular/core';
export type TypedSimpleChange<T> = Omit<SimpleChange, SimpleChange['previousValue'] | SimpleChange['currentValue']>
& { previousValue: T; currentValue: T };
export type TypedSimpleChanges<T> = {
[K in keyof T]: TypedSimpleChange<T[K]>;
};

4
frontend/src/app/framework/angular/image-source.directive.ts

@ -5,7 +5,7 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { AfterViewInit, Directive, ElementRef, Input, NgZone, OnChanges, OnDestroy, OnInit, Renderer2 } from '@angular/core';
import { AfterViewInit, Directive, ElementRef, Input, NgZone, OnDestroy, OnInit, Renderer2 } from '@angular/core';
import { MathHelper, ResourceOwner, StringHelper } from '@app/framework/internal';
const LAYOUT_CACHE: { [key: string]: { width: number; height: number } } = {};
@ -13,7 +13,7 @@ const LAYOUT_CACHE: { [key: string]: { width: number; height: number } } = {};
@Directive({
selector: '[sqxImageSource]',
})
export class ImageSourceDirective extends ResourceOwner implements OnChanges, OnDestroy, OnInit, AfterViewInit {
export class ImageSourceDirective extends ResourceOwner implements OnDestroy, OnInit, AfterViewInit {
private size: any;
private loadTimer: any;
private loadRetries = 0;

4
frontend/src/app/framework/angular/image-url.directive.ts

@ -5,13 +5,13 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Directive, ElementRef, HostBinding, Input, NgZone, OnChanges, OnInit, Renderer2 } from '@angular/core';
import { Directive, ElementRef, HostBinding, Input, NgZone, OnInit, Renderer2 } from '@angular/core';
import { ResourceOwner } from '@app/framework/internal';
@Directive({
selector: '[sqxImageUrl]',
})
export class ImageUrlDirective extends ResourceOwner implements OnChanges, OnInit {
export class ImageUrlDirective extends ResourceOwner implements OnInit {
@Input('sqxImageUrl') @HostBinding('attr.src')
public imageUrl!: string;

4
frontend/src/app/framework/angular/language-selector.component.ts

@ -5,7 +5,7 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, OnInit, Output } from '@angular/core';
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { ModalModel, RelativePosition } from '@app/framework/internal';
export interface Language { iso2Code: string; englishName: string; isMasterLanguage?: boolean }
@ -16,7 +16,7 @@ export interface Language { iso2Code: string; englishName: string; isMasterLangu
templateUrl: './language-selector.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class LanguageSelectorComponent implements OnChanges, OnInit {
export class LanguageSelectorComponent implements OnInit {
@Output()
public languageChange = new EventEmitter<any>();

12
frontend/src/app/framework/angular/markdown.directive.spec.ts

@ -52,7 +52,15 @@ describe('MarkdownDirective', () => {
verifyHtmlRender('<strong>bold</strong>');
});
it('should render as HTML if allowed', () => {
it('should render as HTML if it has encoded characters', () => {
markdownDirective.inline = false;
markdownDirective.markdown = '\'Example\'';
markdownDirective.ngOnChanges();
verifyHtmlRender('<p>&#39;Example&#39;</p>\n');
});
it('should render as HTML if it has tags', () => {
markdownDirective.inline = false;
markdownDirective.markdown = '**bold**';
markdownDirective.ngOnChanges();
@ -60,7 +68,7 @@ describe('MarkdownDirective', () => {
verifyHtmlRender('<p><strong>bold</strong></p>\n');
});
it('should render as inline HTML if allowed', () => {
it('should render as inline HTML if it has tags', () => {
markdownDirective.markdown = '!**bold**';
markdownDirective.ngOnChanges();

6
frontend/src/app/framework/angular/markdown.directive.ts

@ -5,13 +5,13 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Directive, ElementRef, Input, OnChanges, Renderer2 } from '@angular/core';
import { Directive, ElementRef, Input, Renderer2 } from '@angular/core';
import { renderMarkdown } from '@app/framework/internal';
@Directive({
selector: '[sqxMarkdown]',
})
export class MarkdownDirective implements OnChanges {
export class MarkdownDirective {
@Input('sqxMarkdown')
public markdown!: string;
@ -46,7 +46,7 @@ export class MarkdownDirective implements OnChanges {
html = renderMarkdown(markdown, this.inline);
}
const hasHtml = html.indexOf('<') >= 0;
const hasHtml = html.indexOf('<') >= 0 || html.indexOf('&') >= 0;
if (hasHtml) {
this.renderer.setProperty(this.element.nativeElement, 'innerHTML', html);

4
frontend/src/app/framework/angular/pager.component.ts

@ -5,7 +5,7 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Output } from '@angular/core';
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
import { PagingInfo } from './../state';
export const PAGE_SIZES: ReadonlyArray<number> = [10, 20, 30, 50];
@ -16,7 +16,7 @@ export const PAGE_SIZES: ReadonlyArray<number> = [10, 20, 30, 50];
templateUrl: './pager.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class PagerComponent implements OnChanges {
export class PagerComponent {
@Output()
public loadTotal = new EventEmitter();

4
frontend/src/app/framework/angular/resized.directive.ts

@ -5,13 +5,13 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Directive, ElementRef, EventEmitter, Input, NgZone, OnChanges, OnDestroy, Output } from '@angular/core';
import { Directive, ElementRef, EventEmitter, Input, NgZone, OnDestroy, Output } from '@angular/core';
import { ResizeListener, ResizeService, ResourceOwner } from '@app/framework/internal';
@Directive({
selector: '[sqxResized], [sqxResizeCondition]',
})
export class ResizedDirective extends ResourceOwner implements OnDestroy, OnChanges, ResizeListener {
export class ResizedDirective extends ResourceOwner implements OnDestroy, ResizeListener {
private condition: ((rect: DOMRect) => boolean) | undefined;
private conditionValue = false;

4
frontend/src/app/framework/angular/scroll-active.directive.ts

@ -5,12 +5,12 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { AfterViewInit, Directive, ElementRef, Input, OnChanges, Renderer2 } from '@angular/core';
import { AfterViewInit, Directive, ElementRef, Input, Renderer2 } from '@angular/core';
@Directive({
selector: '[sqxScrollActive]',
})
export class ScrollActiveDirective implements AfterViewInit, OnChanges {
export class ScrollActiveDirective implements AfterViewInit {
@Input('sqxScrollActive')
public isActive = false;

7
frontend/src/app/framework/angular/template-wrapper.directive.ts

@ -5,12 +5,13 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Directive, EmbeddedViewRef, Input, OnChanges, OnDestroy, OnInit, SimpleChanges, TemplateRef, ViewContainerRef } from '@angular/core';
import { Directive, EmbeddedViewRef, Input, OnDestroy, OnInit, TemplateRef, ViewContainerRef } from '@angular/core';
import { TypedSimpleChanges } from './helpers';
@Directive({
selector: '[sqxTemplateWrapper]',
})
export class TemplateWrapperDirective implements OnDestroy, OnInit, OnChanges {
export class TemplateWrapperDirective implements OnDestroy, OnInit {
@Input()
public item: any;
@ -48,7 +49,7 @@ export class TemplateWrapperDirective implements OnDestroy, OnInit, OnChanges {
this.view = this.viewContainer.createEmbeddedView(this.templateRef, data);
}
public ngOnChanges(changes: SimpleChanges) {
public ngOnChanges(changes: TypedSimpleChanges<this>) {
if (this.view) {
if (changes.item) {
this.view.context.$implicit = this.item;

4
frontend/src/app/framework/angular/title.component.ts

@ -7,7 +7,7 @@
// tslint:disable: readonly-array
import { ChangeDetectionStrategy, Component, Input, OnChanges, OnDestroy } from '@angular/core';
import { ChangeDetectionStrategy, Component, Input, OnDestroy } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { TitleService, Types } from '@app/framework/internal';
@ -16,7 +16,7 @@ import { TitleService, Types } from '@app/framework/internal';
template: '',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class TitleComponent implements OnDestroy, OnChanges {
export class TitleComponent implements OnDestroy {
private previousIndex: undefined | number;
@Input()

4
frontend/src/app/framework/angular/video-player.component.ts

@ -5,7 +5,7 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { AfterViewInit, ChangeDetectionStrategy, Component, ElementRef, Input, OnChanges, OnDestroy, Renderer2, ViewChild } from '@angular/core';
import { AfterViewInit, ChangeDetectionStrategy, Component, ElementRef, Input, OnDestroy, Renderer2, ViewChild } from '@angular/core';
import { ResourceLoaderService } from '@app/framework/internal';
declare const videojs: any;
@ -16,7 +16,7 @@ declare const videojs: any;
templateUrl: './video-player.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class VideoPlayerComponent implements AfterViewInit, OnDestroy, OnChanges {
export class VideoPlayerComponent implements AfterViewInit, OnDestroy {
private player: any;
@Input()

1
frontend/src/app/framework/declarations.ts

@ -41,6 +41,7 @@ export * from './angular/forms/templated-form-array';
export * from './angular/forms/transform-input.directive';
export * from './angular/forms/validators';
export * from './angular/global-error-handler';
export * from './angular/helpers';
export * from './angular/hover-background.directive';
export * from './angular/http/caching.interceptor';
export * from './angular/http/http-extensions';

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

Loading…
Cancel
Save