From 4d26ebfcfc7483ca73d76a93d1fa17ab2f475e31 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Wed, 13 Nov 2019 16:13:12 +0100 Subject: [PATCH] Feature/meta fields (#445) * Meta fields --- .../Schemas/FieldExtensions.cs | 11 +- .../Schemas/FieldProperties.cs | 7 - .../Schemas/Json/JsonSchemaModel.cs | 10 -- .../Schemas/MetaFields.cs | 46 ++++++ .../Schemas/SchemaExtensions.cs | 36 +++-- .../ContentReferencesExtensions.cs | 2 +- .../Contents/Queries/ContentEnricher.cs | 2 +- .../Schemas/Guards/GuardSchema.cs | 20 +-- .../Schemas/Guards/GuardSchemaTests.cs | 33 ++++- frontend/app/features/content/declarations.ts | 3 + frontend/app/features/content/module.ts | 10 ++ .../contents/contents-page.component.html | 26 +--- .../pages/contents/contents-page.component.ts | 4 - .../shared/content-list-cell.directive.ts | 109 +++++++++++++++ .../shared/content-list-field.component.ts | 132 ++++++++++++++++++ .../shared/content-list-header.component.ts | 115 +++++++++++++++ .../shared/content-selector-item.component.ts | 49 ++----- .../content/shared/content.component.html | 66 ++++----- .../content/shared/content.component.scss | 19 ++- .../content/shared/content.component.ts | 66 +++------ .../shared/contents-selector.component.html | 26 ++-- .../shared/contents-selector.component.ts | 4 - .../shared/reference-item.component.scss | 2 + .../shared/reference-item.component.ts | 4 +- .../pages/schema/field-list.component.html | 4 +- .../pages/schema/field-list.component.ts | 25 +++- .../schema/schema-ui-form.component.html | 3 +- .../components/table-header.component.ts | 60 +++----- .../app/shared/services/schemas.service.ts | 68 +++++++-- .../app/shared/state/contents.forms.spec.ts | 19 +-- .../app/shared/utils/array-extensions.spec.ts | 64 +++++++++ frontend/app/shared/utils/array-extensions.ts | 73 ++++++++++ frontend/app/shared/utils/array-helper.ts | 10 ++ 33 files changed, 833 insertions(+), 295 deletions(-) create mode 100644 backend/src/Squidex.Domain.Apps.Core.Model/Schemas/MetaFields.cs create mode 100644 frontend/app/features/content/shared/content-list-cell.directive.ts create mode 100644 frontend/app/features/content/shared/content-list-field.component.ts create mode 100644 frontend/app/features/content/shared/content-list-header.component.ts create mode 100644 frontend/app/shared/utils/array-extensions.spec.ts create mode 100644 frontend/app/shared/utils/array-extensions.ts create mode 100644 frontend/app/shared/utils/array-helper.ts diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldExtensions.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldExtensions.cs index 76ba5da7d..12cd23788 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldExtensions.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldExtensions.cs @@ -31,12 +31,17 @@ namespace Squidex.Domain.Apps.Core.Schemas public static bool IsForApi(this T field, bool withHidden = false) where T : IField { - return (withHidden || !field.IsHidden) && field.RawProperties.IsForApi(); + return (withHidden || !field.IsHidden) && !field.RawProperties.IsUIProperty(); } - public static bool IsForApi(this T properties) where T : FieldProperties + public static bool IsUI(this T field) where T : IField { - return !(properties is UIFieldProperties); + return field.RawProperties is UIFieldProperties; + } + + public static bool IsUIProperty(this T properties) where T : FieldProperties + { + return properties is UIFieldProperties; } public static Schema ReorderFields(this Schema schema, List ids, long? parentId = null) diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldProperties.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldProperties.cs index 3a8af41e5..ae03e8cd4 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldProperties.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldProperties.cs @@ -5,19 +5,12 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System; using System.Collections.ObjectModel; namespace Squidex.Domain.Apps.Core.Schemas { public abstract class FieldProperties : NamedElementPropertiesBase { - [Obsolete] - public bool IsListField { get; set; } - - [Obsolete] - public bool IsReferenceField { get; set; } - public bool IsRequired { get; set; } public string? Placeholder { get; set; } diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/Json/JsonSchemaModel.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/Json/JsonSchemaModel.cs index 8946a9f5b..2d59724cb 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/Json/JsonSchemaModel.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/Json/JsonSchemaModel.cs @@ -108,21 +108,11 @@ namespace Squidex.Domain.Apps.Core.Schemas.Json schema = schema.ConfigureScripts(Scripts); } - if (FieldsInLists == null) - { - FieldsInLists = new FieldNames(Fields.Where(x => x.Properties.IsListField).Select(x => x.Name).ToArray()); - } - if (FieldsInLists?.Count > 0) { schema = schema.ConfigureFieldsInLists(FieldsInLists); } - if (FieldsInReferences == null) - { - FieldsInLists = new FieldNames(Fields.Where(x => x.Properties.IsReferenceField).Select(x => x.Name).ToArray()); - } - if (FieldsInReferences?.Count > 0) { schema = schema.ConfigureFieldsInReferences(FieldsInReferences); diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/MetaFields.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/MetaFields.cs new file mode 100644 index 000000000..c00de35d4 --- /dev/null +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/MetaFields.cs @@ -0,0 +1,46 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Collections.Generic; +using System.Reflection; + +namespace Squidex.Domain.Apps.Core.Schemas +{ + public static class MetaFields + { + private static readonly HashSet AllList = new HashSet(); + + public static ISet All + { + get { return AllList; } + } + + public static readonly string Id = "meta.id"; + public static readonly string Created = "meta.created"; + public static readonly string CreatedByAvatar = "meta.createdBy.avatar"; + public static readonly string CreatedByName = "meta.createdBy.name"; + public static readonly string LastModified = "meta.lastModified"; + public static readonly string LastModifiedByAvatar = "meta.lastModifiedBy.avatar"; + public static readonly string LastModifiedByName = "meta.lastModifiedBy.name"; + public static readonly string Status = "meta.status"; + public static readonly string StatusColor = "meta.status.color"; + public static readonly string Version = "meta.version"; + + static MetaFields() + { + foreach (var field in typeof(MetaFields).GetFields(BindingFlags.Public | BindingFlags.Static)) + { + if (field.FieldType == typeof(string)) + { + var value = field.GetValue(null) as string; + + AllList.Add(value!); + } + } + } + } +} diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/SchemaExtensions.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/SchemaExtensions.cs index 60b5e918e..b17bc0482 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/SchemaExtensions.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/SchemaExtensions.cs @@ -64,23 +64,39 @@ namespace Squidex.Domain.Apps.Core.Schemas return properties.SchemaIds?.Count == 1 ? properties.SchemaIds[0] : Guid.Empty; } - public static IEnumerable ReferenceFields(this Schema schema) + public static IEnumerable ReferencesFields(this Schema schema) { - var references = schema.FieldsInReferences.Select(x => schema.FieldsByName.GetOrDefault(x)).Where(x => x != null).ToList(); + return schema.RootFields(schema.FieldsInReferences); + } - if (references.Any()) - { - return references; - } + public static IEnumerable ListsFields(this Schema schema) + { + return schema.RootFields(schema.FieldsInLists); + } - references = schema.FieldsInLists.Select(x => schema.FieldsByName.GetOrDefault(x)).Where(x => x != null).ToList(); + public static IEnumerable RootFields(this Schema schema, FieldNames names) + { + var hasField = false; - if (references.Any()) + foreach (var name in names) { - return references; + if (schema.FieldsByName.TryGetValue(name, out var field)) + { + hasField = true; + + yield return field; + } } - return schema.Fields.Take(1); + if (!hasField) + { + var first = schema.Fields.FirstOrDefault(x => !x.IsUI()); + + if (first != null) + { + yield return first; + } + } } public static IEnumerable> ResolvingReferences(this Schema schema) diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ContentReferencesExtensions.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ContentReferencesExtensions.cs index e9a7e37d8..f8ac4845c 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ContentReferencesExtensions.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ContentReferencesExtensions.cs @@ -121,7 +121,7 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds sb.Append(value); } - var referenceFields = schema.ReferenceFields(); + var referenceFields = schema.ReferencesFields(); foreach (var referenceField in referenceFields) { diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentEnricher.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentEnricher.cs index 49072fda2..356bd8bbc 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentEnricher.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentEnricher.cs @@ -104,7 +104,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries if (ShouldEnrichWithSchema(context)) { - var referenceFields = schema.SchemaDef.ReferenceFields().ToArray(); + var referenceFields = schema.SchemaDef.ReferencesFields().ToArray(); var schemaName = schema.SchemaDef.Name; var schemaDisplayName = schema.SchemaDef.DisplayNameUnchanged(); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchema.cs b/backend/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchema.cs index 853c524b7..173826c50 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchema.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchema.cs @@ -113,7 +113,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards Validate.It(() => "Cannot configure UI fields.", e => { - ValidateFieldNames(schema, command.FieldsInLists, nameof(command.FieldsInLists), e); + ValidateFieldNames(schema, command.FieldsInLists, nameof(command.FieldsInLists), e, true); ValidateFieldNames(schema, command.FieldsInReferences, nameof(command.FieldsInReferences), e); }); } @@ -162,7 +162,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards } } - ValidateFieldNames(command, command.FieldsInLists, nameof(command.FieldsInLists), e); + ValidateFieldNames(command, command.FieldsInLists, nameof(command.FieldsInLists), e, true); ValidateFieldNames(command, command.FieldsInReferences, nameof(command.FieldsInReferences), e); } @@ -242,7 +242,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards } else { - if (!field.Properties.IsForApi()) + if (field.Properties.IsUIProperty()) { if (field.IsHidden) { @@ -261,7 +261,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards } } - private static void ValidateFieldNames(Schema schema, FieldNames? fields, string path, AddValidation e) + private static void ValidateFieldNames(Schema schema, FieldNames? fields, string path, AddValidation e, bool withMeta = false) { if (fields != null) { @@ -273,15 +273,17 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards fieldIndex++; fieldPrefix = $"{path}[{fieldIndex}]"; + var field = schema.FieldsByName.GetOrDefault(fieldName ?? string.Empty); + if (string.IsNullOrWhiteSpace(fieldName)) { e(Not.Defined("Field"), fieldPrefix); } - else if (!schema.FieldsByName.TryGetValue(fieldName, out var field)) + else if (field == null && (!withMeta || !MetaFields.All.Contains(fieldName))) { e($"Field is not part of the schema.", fieldPrefix); } - else if (!field.IsForApi()) + else if (field?.IsUI() == true) { e($"Field cannot be an UI field.", fieldPrefix); } @@ -297,7 +299,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards } } - private static void ValidateFieldNames(UpsertCommand command, FieldNames? fields, string path, AddValidation e) + private static void ValidateFieldNames(UpsertCommand command, FieldNames? fields, string path, AddValidation e, bool withMeta = false) { if (fields != null) { @@ -315,11 +317,11 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards { e(Not.Defined("Field"), fieldPrefix); } - else if (field == null) + else if (field == null && (!withMeta || !MetaFields.All.Contains(fieldName))) { e($"Field is not part of the schema.", fieldPrefix); } - else if (field?.Properties.IsForApi() != true) + else if (field?.Properties?.IsUIProperty() == true) { e($"Field cannot be an UI field.", fieldPrefix); } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/GuardSchemaTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/GuardSchemaTests.cs index 7413ea9af..f2ca65049 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/GuardSchemaTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/GuardSchemaTests.cs @@ -434,6 +434,21 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards "FieldsInReferences")); } + [Fact] + public void CanCreate_should_throw_exception_if_references_contains_meta_field() + { + var command = new CreateSchema + { + FieldsInLists = null, + FieldsInReferences = new FieldNames("meta.id"), + Name = "new-schema" + }; + + ValidationAssert.Throws(() => GuardSchema.CanCreate(command), + new ValidationError("Field is not part of the schema.", + "FieldsInReferences[1]")); + } + [Fact] public void CanCreate_should_not_throw_exception_if_command_is_valid() { @@ -476,7 +491,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards } } }, - FieldsInLists = new FieldNames("field1"), + FieldsInLists = new FieldNames("field1", "meta.id"), FieldsInReferences = new FieldNames("field1"), Name = "new-schema" }; @@ -528,12 +543,26 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards "FieldsInReferences")); } + [Fact] + public void CanConfigureUIFields_should_throw_exception_if_references_contains_meta_field() + { + var command = new ConfigureUIFields + { + FieldsInLists = null, + FieldsInReferences = new FieldNames("meta.id") + }; + + ValidationAssert.Throws(() => GuardSchema.CanConfigureUIFields(schema_0, command), + new ValidationError("Field is not part of the schema.", + "FieldsInReferences[1]")); + } + [Fact] public void CanConfigureUIFields_should_not_throw_exception_if_command_is_valid() { var command = new ConfigureUIFields { - FieldsInLists = new FieldNames("field1"), + FieldsInLists = new FieldNames("field1", "meta.id"), FieldsInReferences = new FieldNames("field2") }; diff --git a/frontend/app/features/content/declarations.ts b/frontend/app/features/content/declarations.ts index 952e04938..60a13a382 100644 --- a/frontend/app/features/content/declarations.ts +++ b/frontend/app/features/content/declarations.ts @@ -17,6 +17,9 @@ export * from './pages/schemas/schemas-page.component'; export * from './shared/array-editor.component'; export * from './shared/array-item.component'; export * from './shared/assets-editor.component'; +export * from './shared/content-list-cell.directive'; +export * from './shared/content-list-field.component'; +export * from './shared/content-list-header.component'; export * from './shared/content.component'; export * from './shared/content-status.component'; export * from './shared/content-value.component'; diff --git a/frontend/app/features/content/module.ts b/frontend/app/features/content/module.ts index 31430c418..f590107f2 100644 --- a/frontend/app/features/content/module.ts +++ b/frontend/app/features/content/module.ts @@ -27,7 +27,12 @@ import { ContentComponent, ContentFieldComponent, ContentHistoryPageComponent, + ContentListCellDirective, + ContentListFieldComponent, + ContentListHeaderComponent, + ContentListWidthPipe, ContentPageComponent, + ContentReferencesWidthPipe, ContentSelectorItemComponent, ContentsFiltersPageComponent, ContentsPageComponent, @@ -112,6 +117,11 @@ const routes: Routes = [ CommentsPageComponent, ContentComponent, ContentFieldComponent, + ContentListCellDirective, + ContentReferencesWidthPipe, + ContentListWidthPipe, + ContentListFieldComponent, + ContentListHeaderComponent, ContentHistoryPageComponent, ContentPageComponent, ContentSelectorItemComponent, diff --git a/frontend/app/features/content/pages/contents/contents-page.component.html b/frontend/app/features/content/pages/contents/contents-page.component.html index e543f7b81..e5cdc9a01 100644 --- a/frontend/app/features/content/pages/contents/contents-page.component.html +++ b/frontend/app/features/content/pages/contents/contents-page.component.html @@ -39,7 +39,7 @@
- +
- - - @@ -96,7 +83,7 @@
-
@@ -48,26 +48,13 @@ Actions - - - + - - - - +
+
+ [schema]="schema">
diff --git a/frontend/app/features/content/pages/contents/contents-page.component.ts b/frontend/app/features/content/pages/contents/contents-page.component.ts index 8e09390d0..488e55e1d 100644 --- a/frontend/app/features/content/pages/contents/contents-page.component.ts +++ b/frontend/app/features/content/pages/contents/contents-page.component.ts @@ -50,8 +50,6 @@ export class ContentsPageComponent extends ResourceOwner implements OnInit { public queryModel: QueryModel; public queries: Queries; - public minWidth: string; - @ViewChild('dueTimeSelector', { static: false }) public dueTimeSelector: DueTimeSelectorComponent; @@ -72,8 +70,6 @@ export class ContentsPageComponent extends ResourceOwner implements OnInit { this.schema = schema; - this.minWidth = `${300 + (200 * this.schema.listFields.length)}px`; - this.contentsState.load(); this.updateQueries(); diff --git a/frontend/app/features/content/shared/content-list-cell.directive.ts b/frontend/app/features/content/shared/content-list-cell.directive.ts new file mode 100644 index 000000000..8a229e623 --- /dev/null +++ b/frontend/app/features/content/shared/content-list-cell.directive.ts @@ -0,0 +1,109 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. + */ + +import { Directive, ElementRef, Input, OnChanges, Pipe, PipeTransform, Renderer2 } from '@angular/core'; + +import { + MetaFields, + RootFieldDto, + SchemaDetailsDto, + TableField, + Types +} from '@app/shared'; + +export function getTableWidth(fields: ReadonlyArray) { + let result = 0; + + for (let field of fields) { + result += getCellWidth(field); + } + + return result; +} + +export function getCellWidth(field: TableField) { + if (Types.is(field, RootFieldDto)) { + return 220; + } else { + switch (field) { + case MetaFields.id: + return 280; + case MetaFields.created: + return 150; + case MetaFields.createdByAvatar: + return 55; + case MetaFields.createdByName: + return 150; + case MetaFields.lastModified: + return 150; + case MetaFields.lastModifiedByAvatar: + return 55; + case MetaFields.lastModifiedByName: + return 150; + case MetaFields.status: + return 160; + case MetaFields.statusColor: + return 50; + case MetaFields.version: + return 80; + } + + return 0; + } +} + +@Pipe({ + name: 'sqxContentListWidth', + pure: true +}) +export class ContentListWidthPipe implements PipeTransform { + public transform(value: SchemaDetailsDto) { + if (!value) { + return 0; + } + + return `${getTableWidth(value.referenceFields) + 100}px`; + } +} + +@Pipe({ + name: 'sqxContentReferencesWidth', + pure: true +}) +export class ContentReferencesWidthPipe implements PipeTransform { + public transform(value: SchemaDetailsDto) { + if (!value) { + return 0; + } + + return `${getTableWidth(value.referenceFields) + 300}px`; + } +} + +@Directive({ + selector: '[sqxContentListCell]' +}) +export class ContentListCellDirective implements OnChanges { + @Input('sqxContentListCell') + public field: TableField; + + constructor( + private readonly element: ElementRef, + private readonly renderer: Renderer2 + ) { + } + + public ngOnChanges() { + if (Types.isString(this.field) && this.field) { + const width = `${getCellWidth(this.field)}px`; + + this.renderer.setStyle(this.element.nativeElement, 'min-width', width); + this.renderer.setStyle(this.element.nativeElement, 'max-width', width); + this.renderer.setStyle(this.element.nativeElement, 'width', width); + } + } +} \ No newline at end of file diff --git a/frontend/app/features/content/shared/content-list-field.component.ts b/frontend/app/features/content/shared/content-list-field.component.ts new file mode 100644 index 000000000..0d586e34f --- /dev/null +++ b/frontend/app/features/content/shared/content-list-field.component.ts @@ -0,0 +1,132 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. + */ + +import { ChangeDetectionStrategy, Component, Input, OnChanges } from '@angular/core'; +import { FormGroup } from '@angular/forms'; + +import { + ContentDto, + getContentValue, + LanguageDto, + MetaFields, + RootFieldDto, + TableField, + Types +} from '@app/shared'; + +@Component({ + selector: 'sqx-content-list-field', + template: ` + + + {{content.id}} + + + {{content.created | sqxFromNow}} + + + + + + {{content.createdBy | sqxUserNameRef}} + + + {{content.lastModified | sqxFromNow}} + + + + + + {{content.lastModifiedBy | sqxUserNameRef}} + + + + + + + {{content.status}} + + + + + + + + {{content.version.value}} + + + + + + + + + + + `, + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ContentListFieldComponent implements OnChanges { + @Input() + public field: TableField; + + @Input() + public content: ContentDto; + + @Input() + public patchAllowed: boolean; + + @Input() + public patchForm: FormGroup; + + @Input() + public language: LanguageDto; + + public value: any; + + public ngOnChanges() { + this.reset(); + } + + public reset() { + if (Types.is(this.field, RootFieldDto)) { + const { value, formatted } = getContentValue(this.content, this.language, this.field); + + if (this.patchForm) { + const formControl = this.patchForm.controls[this.field.name]; + + if (formControl) { + formControl.setValue(value); + } + } + + this.value = formatted; + } + } + + public get metaFields() { + return MetaFields; + } + + public get isInlineEditable() { + return Types.is(this.field, RootFieldDto) ? this.field.isInlineEditable : false; + } + + public get fieldName() { + return Types.is(this.field, RootFieldDto) ? this.field.name : this.field; + } +} \ No newline at end of file diff --git a/frontend/app/features/content/shared/content-list-header.component.ts b/frontend/app/features/content/shared/content-list-header.component.ts new file mode 100644 index 000000000..66cdeb679 --- /dev/null +++ b/frontend/app/features/content/shared/content-list-header.component.ts @@ -0,0 +1,115 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. + */ + +import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; + +import { + LanguageDto, + MetaFields, + Query, + RootFieldDto, + TableField, + Types +} from '@app/shared'; + +@Component({ + selector: 'sqx-content-list-header', + template: ` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + `, + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ContentListHeaderComponent { + @Input() + public field: TableField; + + @Output() + public queryChange = new EventEmitter(); + + @Input() + public query: Query; + + @Input() + public language: LanguageDto; + + public get metaFields() { + return MetaFields; + } + + public get isSortable() { + return Types.is(this.field, RootFieldDto) ? this.field.properties.isSortable : false; + } + + public get fieldName() { + return Types.is(this.field, RootFieldDto) ? this.field.name : this.field; + } + + public get fieldDisplayName() { + return Types.is(this.field, RootFieldDto) ? this.field.displayName : ''; + } + + public get fieldPath() { + if (Types.isString(this.field)) { + return this.field; + } else if (this.field.isLocalizable && this.language) { + return `data.${this.field.name}.${this.language.iso2Code}`; + } else { + return `data.${this.field.name}.iv`; + } + } +} \ No newline at end of file diff --git a/frontend/app/features/content/shared/content-selector-item.component.ts b/frontend/app/features/content/shared/content-selector-item.component.ts index a18cdbcb8..f7fd82b0c 100644 --- a/frontend/app/features/content/shared/content-selector-item.component.ts +++ b/frontend/app/features/content/shared/content-selector-item.component.ts @@ -5,13 +5,12 @@ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ -import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core'; +import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; import { ContentDto, - getContentValue, LanguageDto, - RootFieldDto + SchemaDetailsDto } from '@app/shared'; /* tslint:disable:component-selector */ @@ -27,31 +26,19 @@ import { (ngModelChange)="emitSelectedChange($event)" /> - - + + - - - - - - - - - {{content.lastModified | sqxFromNow}} + + `, changeDetection: ChangeDetectionStrategy.OnPush }) -export class ContentSelectorItemComponent implements OnChanges { +export class ContentSelectorItemComponent { @Output() public selectedChange = new EventEmitter(); @@ -65,19 +52,11 @@ export class ContentSelectorItemComponent implements OnChanges { public language: LanguageDto; @Input() - public fields: ReadonlyArray; + public schema: SchemaDetailsDto; @Input('sqxContentSelectorItem') public content: ContentDto; - public values: ReadonlyArray = []; - - public ngOnChanges(changes: SimpleChanges) { - if (changes['content'] || changes['language']) { - this.updateValues(); - } - } - public toggle() { if (this.selectable) { this.emitSelectedChange(!this.selected); @@ -87,16 +66,4 @@ export class ContentSelectorItemComponent implements OnChanges { public emitSelectedChange(isSelected: boolean) { this.selectedChange.emit(isSelected); } - - private updateValues() { - const values = []; - - for (const field of this.fields) { - const { formatted } = getContentValue(this.content, this.language, field); - - values.push(formatted); - } - - this.values = values; - } } \ No newline at end of file diff --git a/frontend/app/features/content/shared/content.component.html b/frontend/app/features/content/shared/content.component.html index 2fcf6279f..9103d3c30 100644 --- a/frontend/app/features/content/shared/content.component.html +++ b/frontend/app/features/content/shared/content.component.html @@ -1,12 +1,24 @@ - + + + +
+ + + +
+
- -