From e14bd3db78304d59afa1cd43740769adb0c74564 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Thu, 25 Jun 2020 16:32:12 +0200 Subject: [PATCH] Ui/sections (#541) * Field sections. * Just some minor things. * Final fixes for sections. * Another fix. * Build fix. --- .../Schemas/Commands/UpsertSchemaField.cs | 3 +- frontend/app/features/content/declarations.ts | 3 + frontend/app/features/content/module.ts | 5 +- .../content/content-field.component.html | 52 ++++------- .../pages/content/content-field.component.ts | 80 +++++++---------- .../pages/content/content-page.component.html | 11 ++- .../pages/content/content-page.component.ts | 7 +- .../content/content-section.component.html | 30 +++++++ .../content/content-section.component.scss | 23 +++++ .../content/content-section.component.ts | 75 ++++++++++++++++ .../pages/schemas/schemas-page.component.html | 5 +- .../shared/forms/array-editor.component.ts | 12 +-- .../shared/forms/array-item.component.html | 18 ++-- .../shared/forms/array-item.component.ts | 86 ++++++------------- .../shared/forms/array-section.component.html | 18 ++++ .../shared/forms/array-section.component.scss | 9 ++ .../shared/forms/array-section.component.ts | 55 ++++++++++++ .../shared/forms/field-editor.component.html | 28 +++--- .../shared/forms/field-editor.component.ts | 26 ++++-- .../content/shared/group-fields.pipe.ts | 45 ++++++++++ .../references/content-creator.component.html | 12 ++- .../components/schema-category.component.html | 14 +-- .../components/schema-category.component.ts | 59 +++++-------- .../app/shared/services/schemas.service.ts | 4 - frontend/app/shared/services/schemas.types.ts | 8 -- 25 files changed, 429 insertions(+), 259 deletions(-) create mode 100644 frontend/app/features/content/pages/content/content-section.component.html create mode 100644 frontend/app/features/content/pages/content/content-section.component.scss create mode 100644 frontend/app/features/content/pages/content/content-section.component.ts create mode 100644 frontend/app/features/content/shared/forms/array-section.component.html create mode 100644 frontend/app/features/content/shared/forms/array-section.component.scss create mode 100644 frontend/app/features/content/shared/forms/array-section.component.ts create mode 100644 frontend/app/features/content/shared/group-fields.pipe.ts diff --git a/backend/src/Squidex.Domain.Apps.Entities/Schemas/Commands/UpsertSchemaField.cs b/backend/src/Squidex.Domain.Apps.Entities/Schemas/Commands/UpsertSchemaField.cs index 3b6184338..0625e2220 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Schemas/Commands/UpsertSchemaField.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Schemas/Commands/UpsertSchemaField.cs @@ -6,13 +6,12 @@ // ========================================================================== using System.Collections.Generic; -using P = Squidex.Domain.Apps.Core.Partitioning; namespace Squidex.Domain.Apps.Entities.Schemas.Commands { public sealed class UpsertSchemaField : UpsertSchemaFieldBase { - public string Partitioning { get; set; } = P.Invariant.Key; + public string Partitioning { get; set; } public List Nested { get; set; } } diff --git a/frontend/app/features/content/declarations.ts b/frontend/app/features/content/declarations.ts index 9fb9c819b..1b1fee9df 100644 --- a/frontend/app/features/content/declarations.ts +++ b/frontend/app/features/content/declarations.ts @@ -10,6 +10,7 @@ export * from './pages/content/content-event.component'; export * from './pages/content/content-field.component'; export * from './pages/content/content-history-page.component'; export * from './pages/content/content-page.component'; +export * from './pages/content/content-section.component'; export * from './pages/content/field-languages.component'; export * from './pages/contents/contents-filters-page.component'; export * from './pages/contents/contents-page.component'; @@ -19,9 +20,11 @@ export * from './shared/content-status.component'; export * from './shared/due-time-selector.component'; export * from './shared/forms/array-editor.component'; export * from './shared/forms/array-item.component'; +export * from './shared/forms/array-section.component'; export * from './shared/forms/assets-editor.component'; export * from './shared/forms/field-editor.component'; export * from './shared/forms/stock-photo-editor.component'; +export * from './shared/group-fields.pipe'; export * from './shared/list/content-list-cell.directive'; export * from './shared/list/content-list-field.component'; export * from './shared/list/content-list-header.component'; diff --git a/frontend/app/features/content/module.ts b/frontend/app/features/content/module.ts index bb3a6fa5d..b94c13a98 100644 --- a/frontend/app/features/content/module.ts +++ b/frontend/app/features/content/module.ts @@ -10,7 +10,7 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { CanDeactivateGuard, ContentMustExistGuard, LoadLanguagesGuard, SchemaMustExistPublishedGuard, SchemaMustNotBeSingletonGuard, SqxFrameworkModule, SqxSharedModule, UnsetContentGuard } from '@app/shared'; -import { ArrayEditorComponent, ArrayItemComponent, AssetsEditorComponent, CommentsPageComponent, ContentComponent, ContentCreatorComponent, ContentEventComponent, ContentFieldComponent, ContentHistoryPageComponent, ContentListCellDirective, ContentListFieldComponent, ContentListHeaderComponent, ContentListWidthPipe, ContentPageComponent, ContentSelectorComponent, ContentSelectorItemComponent, ContentsFiltersPageComponent, ContentsPageComponent, ContentStatusComponent, ContentValueComponent, ContentValueEditorComponent, CustomViewEditorComponent, DueTimeSelectorComponent, FieldEditorComponent, FieldLanguagesComponent, PreviewButtonComponent, ReferenceItemComponent, ReferencesEditorComponent, SchemasPageComponent, StockPhotoEditorComponent } from './declarations'; +import { ArrayEditorComponent, ArrayItemComponent, ArraySectionComponent, AssetsEditorComponent, CommentsPageComponent, ContentComponent, ContentCreatorComponent, ContentEventComponent, ContentFieldComponent, ContentHistoryPageComponent, ContentListCellDirective, ContentListFieldComponent, ContentListHeaderComponent, ContentListWidthPipe, ContentPageComponent, ContentSectionComponent, ContentSelectorComponent, ContentSelectorItemComponent, ContentsFiltersPageComponent, ContentsPageComponent, ContentStatusComponent, ContentValueComponent, ContentValueEditorComponent, CustomViewEditorComponent, DueTimeSelectorComponent, FieldEditorComponent, FieldLanguagesComponent, GroupFieldsPipe, PreviewButtonComponent, ReferenceItemComponent, ReferencesEditorComponent, SchemasPageComponent, StockPhotoEditorComponent } from './declarations'; const routes: Routes = [ { @@ -76,6 +76,7 @@ const routes: Routes = [ declarations: [ ArrayEditorComponent, ArrayItemComponent, + ArraySectionComponent, AssetsEditorComponent, CommentsPageComponent, ContentComponent, @@ -88,6 +89,7 @@ const routes: Routes = [ ContentListHeaderComponent, ContentListWidthPipe, ContentPageComponent, + ContentSectionComponent, ContentSelectorComponent, ContentSelectorItemComponent, ContentsFiltersPageComponent, @@ -99,6 +101,7 @@ const routes: Routes = [ DueTimeSelectorComponent, FieldEditorComponent, FieldLanguagesComponent, + GroupFieldsPipe, PreviewButtonComponent, ReferenceItemComponent, ReferencesEditorComponent, diff --git a/frontend/app/features/content/pages/content/content-field.component.html b/frontend/app/features/content/pages/content/content-field.component.html index abd4061bb..75482a695 100644 --- a/frontend/app/features/content/pages/content/content-field.component.html +++ b/frontend/app/features/content/pages/content/content-field.component.html @@ -1,15 +1,15 @@
-
+
- @@ -19,52 +19,41 @@
+ [displaySuffix]="prefix(language)">
+ [languages]="languages">
- - - - -
- -
+
@@ -74,32 +63,23 @@
+ [displaySuffix]="prefix(language)">
+ [languages]="languages">
- - - - -
diff --git a/frontend/app/features/content/pages/content/content-field.component.ts b/frontend/app/features/content/pages/content/content-field.component.ts index c3b186536..62cf5dcd8 100644 --- a/frontend/app/features/content/pages/content/content-field.component.ts +++ b/frontend/app/features/content/pages/content/content-field.component.ts @@ -5,9 +5,9 @@ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ -import { Component, DoCheck, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core'; -import { AbstractControl, FormGroup } from '@angular/forms'; -import { AppLanguageDto, AppsState, EditContentForm, fieldInvariant, invalid$, LocalStoreService, RootFieldDto, SchemaDto, TranslationsService, Types, value$ } from '@app/shared'; +import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core'; +import { FormGroup } from '@angular/forms'; +import { AppLanguageDto, AppsState, EditContentForm, fieldInvariant, invalid$, LocalStoreService, RootFieldDto, SchemaDto, StringFieldPropertiesDto, TranslationsService, Types, value$ } from '@app/shared'; import { Observable } from 'rxjs'; import { combineLatest } from 'rxjs/operators'; @@ -16,7 +16,7 @@ import { combineLatest } from 'rxjs/operators'; styleUrls: ['./content-field.component.scss'], templateUrl: './content-field.component.html' }) -export class ContentFieldComponent implements DoCheck, OnChanges { +export class ContentFieldComponent implements OnChanges { @Output() public languageChange = new EventEmitter(); @@ -44,14 +44,24 @@ export class ContentFieldComponent implements DoCheck, OnChanges { @Input() public languages: ReadonlyArray; - public selectedFormControl: AbstractControl; - public selectedFormControlCompare?: AbstractControl; - public showAllControls = false; public isDifferent: Observable; public isInvalid: Observable; - public isTranslatable: boolean; + + public get canTranslate() { + if (this.languages.length <= 1) { + return false; + } + + if (!this.field.isLocalizable) { + return false; + } + + const properties = this.field.properties; + + return Types.is(properties, StringFieldPropertiesDto) && (properties.editor === 'Input' || properties.editor === 'TextArea'); + } constructor( private readonly appsState: AppsState, @@ -61,18 +71,12 @@ export class ContentFieldComponent implements DoCheck, OnChanges { } public ngOnChanges(changes: SimpleChanges) { - if (changes['field']) { - this.showAllControls = this.localStore.getBoolean(this.configKey()); - } + this.showAllControls = this.localStore.getBoolean(this.configKey()); if (changes['fieldForm'] && this.fieldForm) { this.isInvalid = invalid$(this.fieldForm); } - if (changes['fieldForm'] || changes['field'] || changes['languages']) { - this.isTranslatable = this.field.isTranslatable; - } - if ((changes['fieldForm'] || changes['fieldFormCompare']) && this.fieldFormCompare) { this.isDifferent = value$(this.fieldForm).pipe( @@ -81,32 +85,6 @@ export class ContentFieldComponent implements DoCheck, OnChanges { } } - public ngDoCheck() { - if (this.fieldForm) { - const control = this.findControl(this.fieldForm); - - if (this.selectedFormControl !== control) { - if (this.selectedFormControl && Types.isFunction(this.selectedFormControl['_clearChangeFns'])) { - this.selectedFormControl['_clearChangeFns'](); - } - - this.selectedFormControl = control; - } - - if (this.fieldFormCompare) { - const controlCompare = this.findControl(this.fieldFormCompare); - - if (this.selectedFormControlCompare !== controlCompare) { - if (this.selectedFormControlCompare && Types.isFunction(this.selectedFormControlCompare['_clearChangeFns'])) { - this.selectedFormControlCompare['_clearChangeFns'](); - } - - this.selectedFormControlCompare = controlCompare; - } - } - } - } - public changeShowAllControls(showAllControls: boolean) { this.showAllControls = showAllControls; @@ -114,11 +92,11 @@ export class ContentFieldComponent implements DoCheck, OnChanges { } public copy() { - if (this.selectedFormControlCompare && this.fieldFormCompare) { + if (this.fieldFormCompare && this.fieldFormCompare) { if (this.showAllControls) { this.fieldForm.setValue(this.fieldFormCompare.value); } else { - this.selectedFormControl.setValue(this.selectedFormControlCompare.value); + this.getControl()!.setValue(this.getControlCompare()!.value); } } } @@ -163,11 +141,11 @@ export class ContentFieldComponent implements DoCheck, OnChanges { } } - private findControl(form: FormGroup) { + private findControl(form?: FormGroup) { if (this.field.isLocalizable) { - return form.controls[this.language.iso2Code]; + return form?.controls[this.language.iso2Code]; } else { - return form.controls[fieldInvariant]; + return form?.controls[fieldInvariant]; } } @@ -175,11 +153,19 @@ export class ContentFieldComponent implements DoCheck, OnChanges { return `(${language.iso2Code})`; } + public getControl() { + return this.findControl(this.fieldForm); + } + + public getControlCompare() { + return this.findControl(this.fieldFormCompare); + } + public trackByLanguage(index: number, language: AppLanguageDto) { return language.iso2Code; } private configKey() { - return `squidex.schemas.${this.schema.id}.fields.${this.field.fieldId}.show-all`; + return `squidex.schemas.${this.schema?.id}.fields.${this.field?.fieldId}.show-all`; } } \ No newline at end of file diff --git a/frontend/app/features/content/pages/content/content-page.component.html b/frontend/app/features/content/pages/content/content-page.component.html index 2f405a454..5ccb728a8 100644 --- a/frontend/app/features/content/pages/content/content-page.component.html +++ b/frontend/app/features/content/pages/content/content-page.component.html @@ -81,16 +81,15 @@
- - + [schema]="schema" + [section]="section"> +
diff --git a/frontend/app/features/content/pages/content/content-page.component.ts b/frontend/app/features/content/pages/content/content-page.component.ts index c57c39a81..f79dbe732 100644 --- a/frontend/app/features/content/pages/content/content-page.component.ts +++ b/frontend/app/features/content/pages/content/content-page.component.ts @@ -9,9 +9,10 @@ import { Component, OnInit } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; -import { ApiUrlConfig, AppLanguageDto, AuthService, AutoSaveKey, AutoSaveService, CanComponentDeactivate, ContentDto, ContentsState, DialogService, EditContentForm, fadeAnimation, FieldDto, LanguagesState, ModalModel, ResourceOwner, SchemaDetailsDto, SchemasState, TempService, Version } from '@app/shared'; +import { ApiUrlConfig, AppLanguageDto, AuthService, AutoSaveKey, AutoSaveService, CanComponentDeactivate, ContentDto, ContentsState, DialogService, EditContentForm, fadeAnimation, LanguagesState, ModalModel, ResourceOwner, RootFieldDto, SchemaDetailsDto, SchemasState, TempService, Version } from '@app/shared'; import { Observable, of } from 'rxjs'; import { debounceTime, filter, onErrorResumeNext, tap } from 'rxjs/operators'; +import { FieldSection } from '../../shared/group-fields.pipe'; @Component({ selector: 'sqx-content-page', @@ -249,8 +250,8 @@ export class ContentPageComponent extends ResourceOwner implements CanComponentD } } - public trackByField(field: FieldDto) { - return field.fieldId; + public trackBySection(index: number, section: FieldSection) { + return section.separator?.fieldId; } } diff --git a/frontend/app/features/content/pages/content/content-section.component.html b/frontend/app/features/content/pages/content/content-section.component.html new file mode 100644 index 000000000..cb6409a25 --- /dev/null +++ b/frontend/app/features/content/pages/content/content-section.component.html @@ -0,0 +1,30 @@ +
+
+
+ +
+
+

{{separator!.displayName}}

+ + + {{separator!.properties.hints}} + +
+
+
+ +
+ + +
\ No newline at end of file diff --git a/frontend/app/features/content/pages/content/content-section.component.scss b/frontend/app/features/content/pages/content/content-section.component.scss new file mode 100644 index 000000000..62113def8 --- /dev/null +++ b/frontend/app/features/content/pages/content/content-section.component.scss @@ -0,0 +1,23 @@ +.btn { + & { + width: 2rem; + } + + &:focus { + border-color: transparent; + } +} + +h3 { + margin: 0; +} + +.header { + line-height: 2rem; + margin-bottom: .5rem; + margin-top: 1.5rem; + + h3 { + line-height: 2rem; + } +} \ No newline at end of file diff --git a/frontend/app/features/content/pages/content/content-section.component.ts b/frontend/app/features/content/pages/content/content-section.component.ts new file mode 100644 index 000000000..c181d0fed --- /dev/null +++ b/frontend/app/features/content/pages/content/content-section.component.ts @@ -0,0 +1,75 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. + */ + +import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Output } from '@angular/core'; +import { AppLanguageDto, EditContentForm, LocalStoreService, RootFieldDto, SchemaDto } from '@app/shared'; +import { FieldSection } from './../../shared/group-fields.pipe'; + +@Component({ + selector: 'sqx-content-section', + styleUrls: ['./content-section.component.scss'], + templateUrl: './content-section.component.html', + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ContentSectionComponent implements OnChanges { + @Output() + public languageChange = new EventEmitter(); + + @Input() + public form: EditContentForm; + + @Input() + public formCompare?: EditContentForm; + + @Input() + public formContext: any; + + @Input() + public schema: SchemaDto; + + @Input() + public section: FieldSection; + + @Input() + public language: AppLanguageDto; + + @Input() + public languages: ReadonlyArray; + + public isCollapsed: boolean; + + constructor( + private readonly localStore: LocalStoreService + ) { + } + + public ngOnChanges() { + this.isCollapsed = this.localStore.getBoolean(this.configKey()); + } + + public toggle() { + this.isCollapsed = !this.isCollapsed; + + this.localStore.setBoolean(this.configKey(), this.isCollapsed); + } + + public getFieldForm(field: RootFieldDto) { + return this.form.form.get(field.name)!; + } + + public getFieldFormCompare(field: RootFieldDto) { + return this.formCompare?.form.get(field.name)!; + } + + public trackByField(index: number, field: RootFieldDto) { + return field.fieldId; + } + + private configKey(): string { + return `squidex.schemas.${this.schema?.id}.fields.${this.section?.separator?.fieldId}.closed`; + } +} \ No newline at end of file diff --git a/frontend/app/features/content/pages/schemas/schemas-page.component.html b/frontend/app/features/content/pages/schemas/schemas-page.component.html index d2ff4aacf..5729e3625 100644 --- a/frontend/app/features/content/pages/schemas/schemas-page.component.html +++ b/frontend/app/features/content/pages/schemas/schemas-page.component.html @@ -9,10 +9,7 @@ - - + diff --git a/frontend/app/features/content/shared/forms/array-editor.component.ts b/frontend/app/features/content/shared/forms/array-editor.component.ts index 30011e966..7102d6333 100644 --- a/frontend/app/features/content/shared/forms/array-editor.component.ts +++ b/frontend/app/features/content/shared/forms/array-editor.component.ts @@ -52,20 +52,20 @@ export class ArrayEditorComponent { } public collapseAll() { - this.children.forEach(component => { - component.collapse(); + this.children.forEach(child => { + child.collapse(); }); } public expandAll() { - this.children.forEach(component => { - component.expand(); + this.children.forEach(child => { + child.expand(); }); } private reset() { - this.children.forEach(component => { - component.reset(); + this.children.forEach(child => { + child.reset(); }); } diff --git a/frontend/app/features/content/shared/forms/array-item.component.html b/frontend/app/features/content/shared/forms/array-item.component.html index e8286a908..19432add3 100644 --- a/frontend/app/features/content/shared/forms/array-item.component.html +++ b/frontend/app/features/content/shared/forms/array-item.component.html @@ -7,7 +7,7 @@
#{{index + 1}} - {{title}} + {{title | async }}
@@ -23,10 +23,10 @@ - -
@@ -42,16 +42,16 @@
-
-
- +
+ - + [section]="section"> +
\ No newline at end of file diff --git a/frontend/app/features/content/shared/forms/array-item.component.ts b/frontend/app/features/content/shared/forms/array-item.component.ts index 6c03d58d2..bb484ecfb 100644 --- a/frontend/app/features/content/shared/forms/array-item.component.ts +++ b/frontend/app/features/content/shared/forms/array-item.component.ts @@ -5,23 +5,22 @@ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ -import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, OnChanges, OnDestroy, Output, QueryList, SimpleChanges, ViewChildren } from '@angular/core'; -import { AbstractControl, FormGroup } from '@angular/forms'; -import { AppLanguageDto, EditContentForm, FieldDto, FieldFormatter, invalid$, RootFieldDto, value$ } from '@app/shared'; -import { Observable, Subscription } from 'rxjs'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, OnChanges, Output, QueryList, SimpleChanges, ViewChildren } from '@angular/core'; +import { FormGroup } from '@angular/forms'; +import { AppLanguageDto, EditContentForm, FieldFormatter, invalid$, NestedFieldDto, RootFieldDto, value$ } from '@app/shared'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { FieldSection } from './../group-fields.pipe'; +import { ArraySectionComponent } from './array-section.component'; import { FieldEditorComponent } from './field-editor.component'; -type FieldControl = { field: FieldDto, control: AbstractControl }; - @Component({ selector: 'sqx-array-item', styleUrls: ['./array-item.component.scss'], templateUrl: './array-item.component.html', changeDetection: ChangeDetectionStrategy.OnPush }) -export class ArrayItemComponent implements OnChanges, OnDestroy { - private subscription: Subscription; - +export class ArrayItemComponent implements OnChanges { @Output() public remove = new EventEmitter(); @@ -62,85 +61,54 @@ export class ArrayItemComponent implements OnChanges, OnDestroy { public languages: ReadonlyArray; @ViewChildren(FieldEditorComponent) - public editors: QueryList; + public sections: QueryList; - public isHidden = false; + public isCollapsed = false; public isInvalid: Observable; - public title: string; - - public fieldControls: ReadonlyArray = []; + public title: Observable; constructor( private readonly changeDetector: ChangeDetectorRef ) { } - public ngOnDestroy() { - this.unsubscribeFromForm(); - } - - private unsubscribeFromForm() { - if (this.subscription) { - this.subscription.unsubscribe(); - } - } - public ngOnChanges(changes: SimpleChanges) { if (changes['itemForm']) { this.isInvalid = invalid$(this.itemForm); - - this.unsubscribeFromForm(); - - this.subscription = - value$(this.itemForm) - .subscribe(() => { - this.updateTitle(); - }); } if (changes['itemForm'] || changes['field']) { - this.updateFields(); - this.updateTitle(); + this.title = value$(this.itemForm).pipe(map(x => this.getTitle(x))); } } - private updateFields() { - const fields: FieldControl[] = []; + private getTitle(value: any) { + const values: string[] = []; for (const field of this.field.nested) { - const control = this.itemForm.get(field.name)!; - - if (control || this.field.properties.isContentField) { - fields.push({ field, control }); - } - } - - this.fieldControls = fields; - } - - private updateTitle() { - const values: string[] = []; + const control = this.itemForm.get(field.name); - for (const { control, field } of this.fieldControls) { - const formatted = FieldFormatter.format(field, control.value); + if (control) { + const formatted = FieldFormatter.format(field, control.value); - if (formatted) { - values.push(formatted); + if (formatted) { + values.push(formatted); + } } } - this.title = values.join(', '); + return values.join(', '); } public collapse() { - this.isHidden = true; + this.isCollapsed = true; this.changeDetector.markForCheck(); } public expand() { - this.isHidden = false; + this.isCollapsed = false; this.changeDetector.markForCheck(); } @@ -162,12 +130,12 @@ export class ArrayItemComponent implements OnChanges, OnDestroy { } public reset() { - this.editors.forEach(editor => { - editor.reset(); + this.sections.forEach(section => { + section.reset(); }); } - public trackByField(index: number, control: FieldControl) { - return control.field.name; + public trackBySection(index: number, section: FieldSection) { + return section.separator?.fieldId; } } \ No newline at end of file diff --git a/frontend/app/features/content/shared/forms/array-section.component.html b/frontend/app/features/content/shared/forms/array-section.component.html new file mode 100644 index 000000000..6aea12189 --- /dev/null +++ b/frontend/app/features/content/shared/forms/array-section.component.html @@ -0,0 +1,18 @@ +
+

{{separator!.displayName}}

+ + + {{separator!.properties.hints}} + +
+ +
+ + +
\ No newline at end of file diff --git a/frontend/app/features/content/shared/forms/array-section.component.scss b/frontend/app/features/content/shared/forms/array-section.component.scss new file mode 100644 index 000000000..2a21a344e --- /dev/null +++ b/frontend/app/features/content/shared/forms/array-section.component.scss @@ -0,0 +1,9 @@ +.header { + line-height: 2rem; + margin-bottom: .5rem; + margin-top: 1.5rem; + + h3 { + line-height: 2rem; + } +} \ No newline at end of file diff --git a/frontend/app/features/content/shared/forms/array-section.component.ts b/frontend/app/features/content/shared/forms/array-section.component.ts new file mode 100644 index 000000000..8120477fd --- /dev/null +++ b/frontend/app/features/content/shared/forms/array-section.component.ts @@ -0,0 +1,55 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. + */ + +import { ChangeDetectionStrategy, Component, Input, QueryList, ViewChildren } from '@angular/core'; +import { FormGroup } from '@angular/forms'; +import { AppLanguageDto, EditContentForm, NestedFieldDto } from '@app/shared'; +import { FieldSection } from './../group-fields.pipe'; +import { FieldEditorComponent } from './field-editor.component'; + +@Component({ + selector: 'sqx-array-section', + styleUrls: ['./array-section.component.scss'], + templateUrl: './array-section.component.html', + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ArraySectionComponent { + @Input() + public itemForm: FormGroup; + + @Input() + public form: EditContentForm; + + @Input() + public formContext: any; + + @Input() + public language: AppLanguageDto; + + @Input() + public languages: ReadonlyArray; + + @Input() + public section: FieldSection; + + @ViewChildren(FieldEditorComponent) + public editors: QueryList; + + public getControl(field: NestedFieldDto) { + return this.itemForm.get(field.name)!; + } + + public reset() { + this.editors.forEach(editor => { + editor.reset(); + }); + } + + public trackByField(index: number, field: NestedFieldDto) { + return field.fieldId; + } +} \ No newline at end of file diff --git a/frontend/app/features/content/shared/forms/field-editor.component.html b/frontend/app/features/content/shared/forms/field-editor.component.html index e12d96e33..841b44cc1 100644 --- a/frontend/app/features/content/shared/forms/field-editor.component.html +++ b/frontend/app/features/content/shared/forms/field-editor.component.html @@ -1,13 +1,11 @@ -
- - - - Disabled - - - +
+ + + Disabled + +
@@ -172,11 +170,9 @@ -
+
- - - {{hints}} - - + + {{field.properties.hints}} +
diff --git a/frontend/app/features/content/shared/forms/field-editor.component.ts b/frontend/app/features/content/shared/forms/field-editor.component.ts index 8498fcb4d..30e816e38 100644 --- a/frontend/app/features/content/shared/forms/field-editor.component.ts +++ b/frontend/app/features/content/shared/forms/field-editor.component.ts @@ -5,7 +5,7 @@ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ -import { Component, ElementRef, Input, ViewChild } from '@angular/core'; +import { Component, ElementRef, Input, OnChanges, SimpleChanges, ViewChild } from '@angular/core'; import { AbstractControl, FormArray, FormControl } from '@angular/forms'; import { AppLanguageDto, EditContentForm, FieldDto, MathHelper, RootFieldDto, Types } from '@app/shared'; @@ -14,7 +14,7 @@ import { AppLanguageDto, EditContentForm, FieldDto, MathHelper, RootFieldDto, Ty styleUrls: ['./field-editor.component.scss'], templateUrl: './field-editor.component.html' }) -export class FieldEditorComponent { +export class FieldEditorComponent implements OnChanges { @Input() public form: EditContentForm; @@ -53,13 +53,25 @@ export class FieldEditorComponent { public uniqueId = MathHelper.guid(); - public reset() { - if (this.editor.nativeElement && Types.isFunction(this.editor.nativeElement['reset'])) { - this.editor.nativeElement['reset'](); + public ngOnChanges(changes: SimpleChanges) { + const previousControl = changes['control']?.previousValue; + + if (previousControl && Types.isFunction(previousControl['_clearChangeFns'])) { + previousControl['_clearChangeFns'](); } + } + + public reset() { + if (this.editor) { + const nativeElement = this.editor.nativeElement; + + if (nativeElement && Types.isFunction(nativeElement['reset'])) { + nativeElement['reset'](); + } - if (this.editor && Types.isFunction(this.editor['reset'])) { - this.editor['reset'](); + if (this.editor && Types.isFunction(this.editor['reset'])) { + this.editor['reset'](); + } } } } \ No newline at end of file diff --git a/frontend/app/features/content/shared/group-fields.pipe.ts b/frontend/app/features/content/shared/group-fields.pipe.ts new file mode 100644 index 000000000..d30e16c96 --- /dev/null +++ b/frontend/app/features/content/shared/group-fields.pipe.ts @@ -0,0 +1,45 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. + */ + +import { Pipe, PipeTransform } from '@angular/core'; +import { FieldDto } from '@app/shared'; + +export interface FieldSection { + separator?: T; + + fields: ReadonlyArray; +} + +@Pipe({ + name: 'sqxGroupFields', + pure: true +}) +export class GroupFieldsPipe implements PipeTransform { + public transform(fields: ReadonlyArray) { + const sections: FieldSection[] = []; + + let currentSeparator: T | undefined = undefined; + let currentFields: T[] = []; + + for (const field of fields) { + if (field.properties.isContentField) { + currentFields.push(field); + } else { + sections.push({ separator: currentSeparator, fields: currentFields }); + + currentFields = []; + currentSeparator = field; + } + } + + if (currentFields.length > 0) { + sections.push({ separator: currentSeparator, fields: currentFields }); + } + + return sections; + } +} \ No newline at end of file diff --git a/frontend/app/features/content/shared/references/content-creator.component.html b/frontend/app/features/content/shared/references/content-creator.component.html index bf52a96b9..a3983b5a6 100644 --- a/frontend/app/features/content/shared/references/content-creator.component.html +++ b/frontend/app/features/content/shared/references/content-creator.component.html @@ -40,16 +40,14 @@
- - + [schema]="schema" + [section]="section"> +
diff --git a/frontend/app/shared/components/schema-category.component.html b/frontend/app/shared/components/schema-category.component.html index 32a167950..bb9dc6217 100644 --- a/frontend/app/shared/components/schema-category.component.html +++ b/frontend/app/shared/components/schema-category.component.html @@ -1,4 +1,4 @@ -
@@ -16,7 +16,7 @@
- ({{snapshot.filtered.length}}) + ({{filteredSchemas.length}})
-
+
-