Browse Source

Ui/sections (#541)

* Field sections.

* Just some minor things.

* Final fixes for sections.

* Another fix.

* Build fix.
pull/544/head
Sebastian Stehle 6 years ago
committed by GitHub
parent
commit
e14bd3db78
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      backend/src/Squidex.Domain.Apps.Entities/Schemas/Commands/UpsertSchemaField.cs
  2. 3
      frontend/app/features/content/declarations.ts
  3. 5
      frontend/app/features/content/module.ts
  4. 52
      frontend/app/features/content/pages/content/content-field.component.html
  5. 80
      frontend/app/features/content/pages/content/content-field.component.ts
  6. 11
      frontend/app/features/content/pages/content/content-page.component.html
  7. 7
      frontend/app/features/content/pages/content/content-page.component.ts
  8. 30
      frontend/app/features/content/pages/content/content-section.component.html
  9. 23
      frontend/app/features/content/pages/content/content-section.component.scss
  10. 75
      frontend/app/features/content/pages/content/content-section.component.ts
  11. 5
      frontend/app/features/content/pages/schemas/schemas-page.component.html
  12. 12
      frontend/app/features/content/shared/forms/array-editor.component.ts
  13. 18
      frontend/app/features/content/shared/forms/array-item.component.html
  14. 86
      frontend/app/features/content/shared/forms/array-item.component.ts
  15. 18
      frontend/app/features/content/shared/forms/array-section.component.html
  16. 9
      frontend/app/features/content/shared/forms/array-section.component.scss
  17. 55
      frontend/app/features/content/shared/forms/array-section.component.ts
  18. 28
      frontend/app/features/content/shared/forms/field-editor.component.html
  19. 26
      frontend/app/features/content/shared/forms/field-editor.component.ts
  20. 45
      frontend/app/features/content/shared/group-fields.pipe.ts
  21. 12
      frontend/app/features/content/shared/references/content-creator.component.html
  22. 14
      frontend/app/shared/components/schema-category.component.html
  23. 59
      frontend/app/shared/components/schema-category.component.ts
  24. 4
      frontend/app/shared/services/schemas.service.ts
  25. 8
      frontend/app/shared/services/schemas.types.ts

3
backend/src/Squidex.Domain.Apps.Entities/Schemas/Commands/UpsertSchemaField.cs

@ -6,13 +6,12 @@
// ========================================================================== // ==========================================================================
using System.Collections.Generic; using System.Collections.Generic;
using P = Squidex.Domain.Apps.Core.Partitioning;
namespace Squidex.Domain.Apps.Entities.Schemas.Commands namespace Squidex.Domain.Apps.Entities.Schemas.Commands
{ {
public sealed class UpsertSchemaField : UpsertSchemaFieldBase public sealed class UpsertSchemaField : UpsertSchemaFieldBase
{ {
public string Partitioning { get; set; } = P.Invariant.Key; public string Partitioning { get; set; }
public List<UpsertSchemaNestedField> Nested { get; set; } public List<UpsertSchemaNestedField> Nested { get; set; }
} }

3
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-field.component';
export * from './pages/content/content-history-page.component'; export * from './pages/content/content-history-page.component';
export * from './pages/content/content-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/content/field-languages.component';
export * from './pages/contents/contents-filters-page.component'; export * from './pages/contents/contents-filters-page.component';
export * from './pages/contents/contents-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/due-time-selector.component';
export * from './shared/forms/array-editor.component'; export * from './shared/forms/array-editor.component';
export * from './shared/forms/array-item.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/assets-editor.component';
export * from './shared/forms/field-editor.component'; export * from './shared/forms/field-editor.component';
export * from './shared/forms/stock-photo-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-cell.directive';
export * from './shared/list/content-list-field.component'; export * from './shared/list/content-list-field.component';
export * from './shared/list/content-list-header.component'; export * from './shared/list/content-list-header.component';

5
frontend/app/features/content/module.ts

@ -10,7 +10,7 @@
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router'; import { RouterModule, Routes } from '@angular/router';
import { CanDeactivateGuard, ContentMustExistGuard, LoadLanguagesGuard, SchemaMustExistPublishedGuard, SchemaMustNotBeSingletonGuard, SqxFrameworkModule, SqxSharedModule, UnsetContentGuard } from '@app/shared'; 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 = [ const routes: Routes = [
{ {
@ -76,6 +76,7 @@ const routes: Routes = [
declarations: [ declarations: [
ArrayEditorComponent, ArrayEditorComponent,
ArrayItemComponent, ArrayItemComponent,
ArraySectionComponent,
AssetsEditorComponent, AssetsEditorComponent,
CommentsPageComponent, CommentsPageComponent,
ContentComponent, ContentComponent,
@ -88,6 +89,7 @@ const routes: Routes = [
ContentListHeaderComponent, ContentListHeaderComponent,
ContentListWidthPipe, ContentListWidthPipe,
ContentPageComponent, ContentPageComponent,
ContentSectionComponent,
ContentSelectorComponent, ContentSelectorComponent,
ContentSelectorItemComponent, ContentSelectorItemComponent,
ContentsFiltersPageComponent, ContentsFiltersPageComponent,
@ -99,6 +101,7 @@ const routes: Routes = [
DueTimeSelectorComponent, DueTimeSelectorComponent,
FieldEditorComponent, FieldEditorComponent,
FieldLanguagesComponent, FieldLanguagesComponent,
GroupFieldsPipe,
PreviewButtonComponent, PreviewButtonComponent,
ReferenceItemComponent, ReferenceItemComponent,
ReferencesEditorComponent, ReferencesEditorComponent,

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

@ -1,15 +1,15 @@
<div class="row no-gutters" [class.compare]="fieldFormCompare"> <div class="row no-gutters" [class.compare]="fieldFormCompare">
<div [class.col-12]="!fieldFormCompare" [class.col-6]="fieldFormCompare"> <div [class.col-12]="!fieldFormCompare" [class.col-6]="fieldFormCompare">
<div class="table-items-row" [class.field-invalid]="isInvalid | async" *ngIf="field.properties.isContentField; else uiField"> <div class="table-items-row" [class.field-invalid]="isInvalid | async">
<div class="languages-buttons"> <div class="languages-buttons">
<button *ngIf="isTranslatable" type="button" class="btn btn-text-secondary btn-sm mr-1" (click)="translate()" title="Autotranslate from master language"> <button *ngIf="canTranslate" type="button" class="btn btn-text-secondary btn-sm mr-1" (click)="translate()" title="Autotranslate from master language">
<i class="icon-translate"></i> <i class="icon-translate"></i>
</button> </button>
<sqx-field-languages <sqx-field-languages
[field]="field" [field]="field"
[language]="language"
(languageChange)="languageChange.emit($event)" (languageChange)="languageChange.emit($event)"
[language]="language"
[languages]="languages" [languages]="languages"
[showAllControls]="showAllControls" [showAllControls]="showAllControls"
(showAllControlsChange)="changeShowAllControls($event)"> (showAllControlsChange)="changeShowAllControls($event)">
@ -19,52 +19,41 @@
<ng-container *ngIf="showAllControls; else singleControl"> <ng-container *ngIf="showAllControls; else singleControl">
<div class="form-group" *ngFor="let language of languages; trackBy: trackByLanguage"> <div class="form-group" *ngFor="let language of languages; trackBy: trackByLanguage">
<sqx-field-editor <sqx-field-editor
[displaySuffix]="prefix(language)" [control]="fieldForm.controls[language.iso2Code]"
[field]="field"
[form]="form" [form]="form"
[formContext]="formContext" [formContext]="formContext"
[field]="field"
[language]="language" [language]="language"
[languages]="languages" [languages]="languages"
[control]="fieldForm.controls[language.iso2Code]"> [displaySuffix]="prefix(language)">
</sqx-field-editor> </sqx-field-editor>
</div> </div>
</ng-container> </ng-container>
<ng-template #singleControl> <ng-template #singleControl>
<sqx-field-editor <sqx-field-editor
[control]="getControl()"
[field]="field"
[form]="form" [form]="form"
[formContext]="formContext" [formContext]="formContext"
[field]="field"
[language]="language" [language]="language"
[languages]="languages" [languages]="languages">
[control]="selectedFormControl">
</sqx-field-editor> </sqx-field-editor>
</ng-template> </ng-template>
</div> </div>
<ng-template #uiField>
<sqx-field-editor
[form]="form"
[formContext]="formContext"
[field]="field"
[language]="language"
[languages]="languages"
[control]="selectedFormControl">
</sqx-field-editor>
</ng-template>
</div> </div>
<div class="col-6 col-right" *ngIf="fieldFormCompare"> <div class="col-6 col-right" *ngIf="fieldFormCompare">
<button type="button" class="btn btn-primary btn-sm field-copy" (click)="copy()" *ngIf="field.properties.isContentField && (isDifferent | async)"> <button type="button" class="btn btn-primary btn-sm field-copy" (click)="copy()" *ngIf="isDifferent | async">
<i class="icon-arrow_back"></i> <i class="icon-arrow_back"></i>
</button> </button>
<div class="table-items-row" *ngIf="field.properties.isContentField; else uiField"> <div class="table-items-row">
<div class="languages-buttons"> <div class="languages-buttons">
<sqx-field-languages <sqx-field-languages
[field]="field" [field]="field"
[language]="language"
(languageChange)="languageChange.emit($event)" (languageChange)="languageChange.emit($event)"
[language]="language"
[languages]="languages" [languages]="languages"
[showAllControls]="showAllControls" [showAllControls]="showAllControls"
(showAllControlsChange)="changeShowAllControls($event)"> (showAllControlsChange)="changeShowAllControls($event)">
@ -74,32 +63,23 @@
<ng-container *ngIf="showAllControls; else singleControlCompare"> <ng-container *ngIf="showAllControls; else singleControlCompare">
<div class="form-group" *ngFor="let language of languages; trackBy: trackByLanguage"> <div class="form-group" *ngFor="let language of languages; trackBy: trackByLanguage">
<sqx-field-editor <sqx-field-editor
[displaySuffix]="prefix(language)" [control]="fieldFormCompare?.controls[language.iso2Code]"
[field]="field" [field]="field"
[language]="language" [language]="language"
[languages]="languages" [languages]="languages"
[control]="fieldFormCompare?.controls[language.iso2Code]"> [displaySuffix]="prefix(language)">
</sqx-field-editor> </sqx-field-editor>
</div> </div>
</ng-container> </ng-container>
<ng-template #singleControlCompare> <ng-template #singleControlCompare>
<sqx-field-editor <sqx-field-editor
[control]="getControlCompare()"
[field]="field" [field]="field"
[language]="language" [language]="language"
[languages]="languages" [languages]="languages">
[control]="selectedFormControlCompare">
</sqx-field-editor> </sqx-field-editor>
</ng-template> </ng-template>
</div> </div>
<ng-template #uiField>
<sqx-field-editor
[field]="field"
[language]="language"
[languages]="languages"
[control]="selectedFormControl">
</sqx-field-editor>
</ng-template>
</div> </div>
</div> </div>

80
frontend/app/features/content/pages/content/content-field.component.ts

@ -5,9 +5,9 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/ */
import { Component, DoCheck, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core'; import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core';
import { AbstractControl, FormGroup } from '@angular/forms'; import { FormGroup } from '@angular/forms';
import { AppLanguageDto, AppsState, EditContentForm, fieldInvariant, invalid$, LocalStoreService, RootFieldDto, SchemaDto, TranslationsService, Types, value$ } from '@app/shared'; import { AppLanguageDto, AppsState, EditContentForm, fieldInvariant, invalid$, LocalStoreService, RootFieldDto, SchemaDto, StringFieldPropertiesDto, TranslationsService, Types, value$ } from '@app/shared';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { combineLatest } from 'rxjs/operators'; import { combineLatest } from 'rxjs/operators';
@ -16,7 +16,7 @@ import { combineLatest } from 'rxjs/operators';
styleUrls: ['./content-field.component.scss'], styleUrls: ['./content-field.component.scss'],
templateUrl: './content-field.component.html' templateUrl: './content-field.component.html'
}) })
export class ContentFieldComponent implements DoCheck, OnChanges { export class ContentFieldComponent implements OnChanges {
@Output() @Output()
public languageChange = new EventEmitter<AppLanguageDto>(); public languageChange = new EventEmitter<AppLanguageDto>();
@ -44,14 +44,24 @@ export class ContentFieldComponent implements DoCheck, OnChanges {
@Input() @Input()
public languages: ReadonlyArray<AppLanguageDto>; public languages: ReadonlyArray<AppLanguageDto>;
public selectedFormControl: AbstractControl;
public selectedFormControlCompare?: AbstractControl;
public showAllControls = false; public showAllControls = false;
public isDifferent: Observable<boolean>; public isDifferent: Observable<boolean>;
public isInvalid: Observable<boolean>; public isInvalid: Observable<boolean>;
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( constructor(
private readonly appsState: AppsState, private readonly appsState: AppsState,
@ -61,18 +71,12 @@ export class ContentFieldComponent implements DoCheck, OnChanges {
} }
public ngOnChanges(changes: SimpleChanges) { 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) { if (changes['fieldForm'] && this.fieldForm) {
this.isInvalid = invalid$(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) { if ((changes['fieldForm'] || changes['fieldFormCompare']) && this.fieldFormCompare) {
this.isDifferent = this.isDifferent =
value$(this.fieldForm).pipe( 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) { public changeShowAllControls(showAllControls: boolean) {
this.showAllControls = showAllControls; this.showAllControls = showAllControls;
@ -114,11 +92,11 @@ export class ContentFieldComponent implements DoCheck, OnChanges {
} }
public copy() { public copy() {
if (this.selectedFormControlCompare && this.fieldFormCompare) { if (this.fieldFormCompare && this.fieldFormCompare) {
if (this.showAllControls) { if (this.showAllControls) {
this.fieldForm.setValue(this.fieldFormCompare.value); this.fieldForm.setValue(this.fieldFormCompare.value);
} else { } 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) { if (this.field.isLocalizable) {
return form.controls[this.language.iso2Code]; return form?.controls[this.language.iso2Code];
} else { } else {
return form.controls[fieldInvariant]; return form?.controls[fieldInvariant];
} }
} }
@ -175,11 +153,19 @@ export class ContentFieldComponent implements DoCheck, OnChanges {
return `(${language.iso2Code})`; return `(${language.iso2Code})`;
} }
public getControl() {
return this.findControl(this.fieldForm);
}
public getControlCompare() {
return this.findControl(this.fieldFormCompare);
}
public trackByLanguage(index: number, language: AppLanguageDto) { public trackByLanguage(index: number, language: AppLanguageDto) {
return language.iso2Code; return language.iso2Code;
} }
private configKey() { 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`;
} }
} }

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

@ -81,16 +81,15 @@
</ng-container> </ng-container>
<div content> <div content>
<sqx-content-field *ngFor="let field of schema.fields; trackBy: trackByField" <sqx-content-section *ngFor="let section of schema.fields | sqxGroupFields; trackBy: trackBySection"
[(language)]="language" [(language)]="language"
[field]="field"
[fieldForm]="contentForm.form.get(field.name)"
[fieldFormCompare]="contentFormCompare?.form.get(field.name)"
[form]="contentForm" [form]="contentForm"
[formCompare]="contentFormCompare"
[formContext]="formContext" [formContext]="formContext"
[languages]="languages" [languages]="languages"
[schema]="schema"> [schema]="schema"
</sqx-content-field> [section]="section">
</sqx-content-section>
</div> </div>
</sqx-list-view> </sqx-list-view>

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

@ -9,9 +9,10 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router'; 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 { Observable, of } from 'rxjs';
import { debounceTime, filter, onErrorResumeNext, tap } from 'rxjs/operators'; import { debounceTime, filter, onErrorResumeNext, tap } from 'rxjs/operators';
import { FieldSection } from '../../shared/group-fields.pipe';
@Component({ @Component({
selector: 'sqx-content-page', selector: 'sqx-content-page',
@ -249,8 +250,8 @@ export class ContentPageComponent extends ResourceOwner implements CanComponentD
} }
} }
public trackByField(field: FieldDto) { public trackBySection(index: number, section: FieldSection<RootFieldDto>) {
return field.fieldId; return section.separator?.fieldId;
} }
} }

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

@ -0,0 +1,30 @@
<div class="header" *ngIf="section.separator; let separator">
<div class="row no-gutters">
<div class="col-auto">
<button type="button" class="btn btn-sm btn-text-secondary" (click)="toggle()">
<i [class.icon-caret-right]="isCollapsed" [class.icon-caret-down]="!isCollapsed"></i>
</button>
</div>
<div class="col">
<h3>{{separator!.displayName}}</h3>
<sqx-form-hint *ngIf="separator!.properties.hints?.length > 0">
{{separator!.properties.hints}}
</sqx-form-hint>
</div>
</div>
</div>
<div [class.hidden]="isCollapsed">
<sqx-content-field *ngFor="let field of section.fields; trackBy: trackByField"
[field]="field"
[fieldForm]="getFieldForm(field)"
[fieldFormCompare]="getFieldFormCompare(field)"
[form]="form"
[formContext]="formContext"
(languageChange)="languageChange.emit($event)"
[language]="language"
[languages]="languages"
[schema]="schema">
</sqx-content-field>
</div>

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

75
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<AppLanguageDto>();
@Input()
public form: EditContentForm;
@Input()
public formCompare?: EditContentForm;
@Input()
public formContext: any;
@Input()
public schema: SchemaDto;
@Input()
public section: FieldSection<RootFieldDto>;
@Input()
public language: AppLanguageDto;
@Input()
public languages: ReadonlyArray<AppLanguageDto>;
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`;
}
}

5
frontend/app/features/content/pages/schemas/schemas-page.component.html

@ -9,10 +9,7 @@
<ng-container header> <ng-container header>
<a class="panel-close btn-collapse" [class.collapsed]="isCollapsed" (click)="toggle()"> <a class="panel-close btn-collapse" [class.collapsed]="isCollapsed" (click)="toggle()">
<i <i [class.icon-angle-double-left]="!isCollapsed" [class.icon-angle-double-right]="isCollapsed"></i>
[class.icon-angle-double-left]="!isCollapsed"
[class.icon-angle-double-right]="isCollapsed">
</i>
</a> </a>
</ng-container> </ng-container>

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

@ -52,20 +52,20 @@ export class ArrayEditorComponent {
} }
public collapseAll() { public collapseAll() {
this.children.forEach(component => { this.children.forEach(child => {
component.collapse(); child.collapse();
}); });
} }
public expandAll() { public expandAll() {
this.children.forEach(component => { this.children.forEach(child => {
component.expand(); child.expand();
}); });
} }
private reset() { private reset() {
this.children.forEach(component => { this.children.forEach(child => {
component.reset(); child.reset();
}); });
} }

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

@ -7,7 +7,7 @@
<div class="col"> <div class="col">
<div class="truncate"> <div class="truncate">
<span class="header-index">#{{index + 1}}</span> <span class="header-index">#{{index + 1}}</span>
<span class="header-title">{{title}}</span> <span class="header-title">{{title | async }}</span>
</div> </div>
</div> </div>
<div class="col-auto pr-4"> <div class="col-auto pr-4">
@ -23,10 +23,10 @@
<button type="button" class="btn btn-text-secondary" [disabled]="isDisabled || isLast" (click)="moveBottom()" title="Move this item to bottom"> <button type="button" class="btn btn-text-secondary" [disabled]="isDisabled || isLast" (click)="moveBottom()" title="Move this item to bottom">
<i class="icon-caret-bottom"></i> <i class="icon-caret-bottom"></i>
</button> </button>
<button type="button" class="btn btn-text-secondary" [class.hidden]="!isHidden" (click)="expand()" title="Expand this item"> <button type="button" class="btn btn-text-secondary" [class.hidden]="!isCollapsed" (click)="expand()" title="Expand this item">
<i class="icon-plus-square"></i> <i class="icon-plus-square"></i>
</button> </button>
<button type="button" class="btn btn-text-secondary" [class.hidden]="isHidden" (click)="collapse()" title="Collapse this item"> <button type="button" class="btn btn-text-secondary" [class.hidden]="isCollapsed" (click)="collapse()" title="Collapse this item">
<i class="icon-minus-square"></i> <i class="icon-minus-square"></i>
</button> </button>
</div> </div>
@ -42,16 +42,16 @@
</div> </div>
</div> </div>
<div class="card-body" [class.hidden]="isHidden"> <div class="card-body" [class.hidden]="isCollapsed">
<div class="form-group" *ngFor="let fieldControl of fieldControls; trackBy: trackByField"> <div class="form-group" *ngFor="let section of field.nested | sqxGroupFields; trackBy: trackBySection">
<sqx-field-editor <sqx-array-section
[form]="form" [form]="form"
[formContext]="formContext" [formContext]="formContext"
[field]="fieldControl.field" [itemForm]="itemForm"
[language]="language" [language]="language"
[languages]="languages" [languages]="languages"
[control]="fieldControl.control"> [section]="section">
</sqx-field-editor> </sqx-array-section>
</div> </div>
</div> </div>
</div> </div>

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

@ -5,23 +5,22 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. * 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 { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, OnChanges, Output, QueryList, SimpleChanges, ViewChildren } from '@angular/core';
import { AbstractControl, FormGroup } from '@angular/forms'; import { FormGroup } from '@angular/forms';
import { AppLanguageDto, EditContentForm, FieldDto, FieldFormatter, invalid$, RootFieldDto, value$ } from '@app/shared'; import { AppLanguageDto, EditContentForm, FieldFormatter, invalid$, NestedFieldDto, RootFieldDto, value$ } from '@app/shared';
import { Observable, Subscription } from 'rxjs'; 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'; import { FieldEditorComponent } from './field-editor.component';
type FieldControl = { field: FieldDto, control: AbstractControl };
@Component({ @Component({
selector: 'sqx-array-item', selector: 'sqx-array-item',
styleUrls: ['./array-item.component.scss'], styleUrls: ['./array-item.component.scss'],
templateUrl: './array-item.component.html', templateUrl: './array-item.component.html',
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class ArrayItemComponent implements OnChanges, OnDestroy { export class ArrayItemComponent implements OnChanges {
private subscription: Subscription;
@Output() @Output()
public remove = new EventEmitter(); public remove = new EventEmitter();
@ -62,85 +61,54 @@ export class ArrayItemComponent implements OnChanges, OnDestroy {
public languages: ReadonlyArray<AppLanguageDto>; public languages: ReadonlyArray<AppLanguageDto>;
@ViewChildren(FieldEditorComponent) @ViewChildren(FieldEditorComponent)
public editors: QueryList<FieldEditorComponent>; public sections: QueryList<ArraySectionComponent>;
public isHidden = false; public isCollapsed = false;
public isInvalid: Observable<boolean>; public isInvalid: Observable<boolean>;
public title: string; public title: Observable<string>;
public fieldControls: ReadonlyArray<FieldControl> = [];
constructor( constructor(
private readonly changeDetector: ChangeDetectorRef private readonly changeDetector: ChangeDetectorRef
) { ) {
} }
public ngOnDestroy() {
this.unsubscribeFromForm();
}
private unsubscribeFromForm() {
if (this.subscription) {
this.subscription.unsubscribe();
}
}
public ngOnChanges(changes: SimpleChanges) { public ngOnChanges(changes: SimpleChanges) {
if (changes['itemForm']) { if (changes['itemForm']) {
this.isInvalid = invalid$(this.itemForm); this.isInvalid = invalid$(this.itemForm);
this.unsubscribeFromForm();
this.subscription =
value$(this.itemForm)
.subscribe(() => {
this.updateTitle();
});
} }
if (changes['itemForm'] || changes['field']) { if (changes['itemForm'] || changes['field']) {
this.updateFields(); this.title = value$(this.itemForm).pipe(map(x => this.getTitle(x)));
this.updateTitle();
} }
} }
private updateFields() { private getTitle(value: any) {
const fields: FieldControl[] = []; const values: string[] = [];
for (const field of this.field.nested) { for (const field of this.field.nested) {
const control = this.itemForm.get(field.name)!; 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[] = [];
for (const { control, field } of this.fieldControls) { if (control) {
const formatted = FieldFormatter.format(field, control.value); const formatted = FieldFormatter.format(field, control.value);
if (formatted) { if (formatted) {
values.push(formatted); values.push(formatted);
}
} }
} }
this.title = values.join(', '); return values.join(', ');
} }
public collapse() { public collapse() {
this.isHidden = true; this.isCollapsed = true;
this.changeDetector.markForCheck(); this.changeDetector.markForCheck();
} }
public expand() { public expand() {
this.isHidden = false; this.isCollapsed = false;
this.changeDetector.markForCheck(); this.changeDetector.markForCheck();
} }
@ -162,12 +130,12 @@ export class ArrayItemComponent implements OnChanges, OnDestroy {
} }
public reset() { public reset() {
this.editors.forEach(editor => { this.sections.forEach(section => {
editor.reset(); section.reset();
}); });
} }
public trackByField(index: number, control: FieldControl) { public trackBySection(index: number, section: FieldSection<NestedFieldDto>) {
return control.field.name; return section.separator?.fieldId;
} }
} }

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

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

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

55
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<AppLanguageDto>;
@Input()
public section: FieldSection<NestedFieldDto>;
@ViewChildren(FieldEditorComponent)
public editors: QueryList<FieldEditorComponent>;
public getControl(field: NestedFieldDto) {
return this.itemForm.get(field.name)!;
}
public reset() {
this.editors.forEach(editor => {
editor.reset();
});
}
public trackByField(index: number, field: NestedFieldDto) {
return field.fieldId;
}
}

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

@ -1,13 +1,11 @@
<div [class.ui]="!field.properties.isContentField" *ngIf="field"> <div *ngIf="field">
<ng-container *ngIf="field.properties.isContentField"> <label>
<label> {{field.displayName}} {{displaySuffix}} <span class="field-required" [class.hidden]="!field.properties.isRequired">*</span>
{{field.displayName}} {{displaySuffix}} <span class="field-required" [class.hidden]="!field.properties.isRequired">*</span> </label>
</label>
<small class="field-disabled pl-1" *ngIf="field.isDisabled">Disabled</small>
<small class="field-disabled pl-1" *ngIf="field.isDisabled">Disabled</small>
<sqx-control-errors *ngIf="form" [for]="editorControl" [fieldName]="field.displayName" [submitted]="form.submitted | async"></sqx-control-errors>
<sqx-control-errors *ngIf="form" [for]="editorControl" [fieldName]="field.displayName" [submitted]="form.submitted | async"></sqx-control-errors>
</ng-container>
<div> <div>
<ng-container *ngIf="field.properties.editorUrl; else noEditor"> <ng-container *ngIf="field.properties.editorUrl; else noEditor">
@ -172,11 +170,9 @@
</ng-container> </ng-container>
</ng-container> </ng-container>
</ng-template> </ng-template>
</div> </div>
<ng-container *ngIf="field.properties.hints; let hints"> <sqx-form-hint *ngIf="field.properties.hints?.length > 0">
<sqx-form-hint *ngIf="hints.length > 0"> {{field.properties.hints}}
{{hints}} </sqx-form-hint>
</sqx-form-hint>
</ng-container>
</div> </div>

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

@ -5,7 +5,7 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/ */
import { 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 { AbstractControl, FormArray, FormControl } from '@angular/forms';
import { AppLanguageDto, EditContentForm, FieldDto, MathHelper, RootFieldDto, Types } from '@app/shared'; 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'], styleUrls: ['./field-editor.component.scss'],
templateUrl: './field-editor.component.html' templateUrl: './field-editor.component.html'
}) })
export class FieldEditorComponent { export class FieldEditorComponent implements OnChanges {
@Input() @Input()
public form: EditContentForm; public form: EditContentForm;
@ -53,13 +53,25 @@ export class FieldEditorComponent {
public uniqueId = MathHelper.guid(); public uniqueId = MathHelper.guid();
public reset() { public ngOnChanges(changes: SimpleChanges) {
if (this.editor.nativeElement && Types.isFunction(this.editor.nativeElement['reset'])) { const previousControl = changes['control']?.previousValue;
this.editor.nativeElement['reset']();
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'])) { if (this.editor && Types.isFunction(this.editor['reset'])) {
this.editor['reset'](); this.editor['reset']();
}
} }
} }
} }

45
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<T> {
separator?: T;
fields: ReadonlyArray<T>;
}
@Pipe({
name: 'sqxGroupFields',
pure: true
})
export class GroupFieldsPipe<T extends FieldDto> implements PipeTransform {
public transform(fields: ReadonlyArray<T>) {
const sections: FieldSection<T>[] = [];
let currentSeparator: T | undefined = undefined;
let currentFields: T[] = [];
for (const field of fields) {
if (field.properties.isContentField) {
currentFields.push(field);
} else {
sections.push({ separator: currentSeparator, fields: currentFields });
currentFields = [];
currentSeparator = field;
}
}
if (currentFields.length > 0) {
sections.push({ separator: currentSeparator, fields: currentFields });
}
return sections;
}
}

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

@ -40,16 +40,14 @@
<ng-container content> <ng-container content>
<ng-container *ngIf="schema && contentForm"> <ng-container *ngIf="schema && contentForm">
<form [formGroup]="contentForm.form" (ngSubmit)="saveAndPublish()"> <form [formGroup]="contentForm.form" (ngSubmit)="saveAndPublish()">
<sqx-content-field *ngFor="let field of schema.fields" <sqx-content-section *ngFor="let section of schema.fields | sqxGroupFields"
(languageChange)="selectLanguage($event)" [(language)]="language"
[field]="field"
[fieldForm]="contentForm.form.get(field.name)"
[form]="contentForm" [form]="contentForm"
[formContext]="contentFormContext" [formContext]="contentFormContext"
[language]="language"
[languages]="languages" [languages]="languages"
[schema]="schema"> [schema]="schema"
</sqx-content-field> [section]="section">
</sqx-content-section>
</form> </form>
</ng-container> </ng-container>
</ng-container> </ng-container>

14
frontend/app/shared/components/schema-category.component.html

@ -1,4 +1,4 @@
<div *ngIf="!forContent || snapshot.filtered.length > 0" class="droppable category" <div *ngIf="!forContent || filteredSchemas.length > 0" class="droppable category"
cdkDropList cdkDropList
cdkDropListSortingDisabled cdkDropListSortingDisabled
[cdkDropListData]="schemaCategory.name" [cdkDropListData]="schemaCategory.name"
@ -8,7 +8,7 @@
<div class="row no-gutters"> <div class="row no-gutters">
<div class="col-auto"> <div class="col-auto">
<button type="button" class="btn btn-sm btn-text-secondary" (click)="toggle()"> <button type="button" class="btn btn-sm btn-text-secondary" (click)="toggle()">
<i [class.icon-caret-right]="!snapshot.isOpen" [class.icon-caret-down]="snapshot.isOpen"></i> <i [class.icon-caret-right]="isCollapsed" [class.icon-caret-down]="!isCollapsed"></i>
</button> </button>
</div> </div>
<div class="col"> <div class="col">
@ -16,7 +16,7 @@
</div> </div>
<div class="col-auto"> <div class="col-auto">
<ng-container *ngIf="schemaCategory.schemas.length > 0; else noSchemas"> <ng-container *ngIf="schemaCategory.schemas.length > 0; else noSchemas">
({{snapshot.filtered.length}}) ({{filteredSchemas.length}})
</ng-container> </ng-container>
<ng-template #noSchemas> <ng-template #noSchemas>
<button type="button" class="btn btn-sm btn-text-secondary btn-remove" (click)="remove.emit()"> <button type="button" class="btn btn-sm btn-text-secondary btn-remove" (click)="remove.emit()">
@ -24,12 +24,12 @@
</button> </button>
</ng-template> </ng-template>
</div> </div>
</div> </div>
</div> </div>
<div class="nav nav-panel nav-dark nav-dark-bordered flex-column" *ngIf="snapshot.isOpen" @fade> <div class="nav nav-panel nav-dark nav-dark-bordered flex-column" *ngIf="!isCollapsed" @fade>
<ng-container *ngIf="!forContent; else simpleMode"> <ng-container *ngIf="!forContent; else simpleMode">
<div *ngFor="let schema of snapshot.filtered; trackBy: trackBySchema" class="nav-item" <div *ngFor="let schema of filteredSchemas; trackBy: trackBySchema" class="nav-item"
routerLinkActive="active" routerLinkActive="active"
cdkDropList cdkDropList
cdkDrag cdkDrag
@ -59,7 +59,7 @@
</ng-container> </ng-container>
<ng-template #simpleMode> <ng-template #simpleMode>
<li *ngFor="let schema of snapshot.filtered; trackBy: trackBySchema" class="nav-item"> <li *ngFor="let schema of filteredSchemas; trackBy: trackBySchema" class="nav-item">
<a class="nav-link" [routerLink]="schemaRoute(schema)" routerLinkActive="active"> <a class="nav-link" [routerLink]="schemaRoute(schema)" routerLinkActive="active">
<span class="schema-name" *ngIf="forContent">{{schema.displayName}}</span> <span class="schema-name" *ngIf="forContent">{{schema.displayName}}</span>
</a> </a>

59
frontend/app/shared/components/schema-category.component.ts

@ -6,16 +6,8 @@
*/ */
import { CdkDragDrop } from '@angular/cdk/drag-drop'; import { CdkDragDrop } from '@angular/cdk/drag-drop';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges } from '@angular/core'; import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Output } from '@angular/core';
import { fadeAnimation, LocalStoreService, SchemaCategory, SchemaDto, SchemasState, StatefulComponent } from '@app/shared/internal'; import { fadeAnimation, LocalStoreService, SchemaCategory, SchemaDto, SchemasList, SchemasState } from '@app/shared/internal';
interface State {
// The filtered schemas.
filtered: ReadonlyArray<SchemaDto>;
// True when the category is open.
isOpen?: boolean;
}
@Component({ @Component({
selector: 'sqx-schema-category', selector: 'sqx-schema-category',
@ -26,7 +18,7 @@ interface State {
], ],
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class SchemaCategoryComponent extends StatefulComponent<State> implements OnInit, OnChanges { export class SchemaCategoryComponent implements OnChanges {
@Output() @Output()
public remove = new EventEmitter(); public remove = new EventEmitter();
@ -39,42 +31,35 @@ export class SchemaCategoryComponent extends StatefulComponent<State> implements
@Input() @Input()
public forContent: boolean; public forContent: boolean;
constructor(changeDetector: ChangeDetectorRef, public filteredSchemas: SchemasList;
public isCollapsed = false;
constructor(
private readonly localStore: LocalStoreService, private readonly localStore: LocalStoreService,
private readonly schemasState: SchemasState private readonly schemasState: SchemasState
) { ) {
super(changeDetector, { filtered: [], isOpen: true });
}
public ngOnInit() {
this.next(s => ({ ...s, isOpen: !this.localStore.getBoolean(this.configKey()) }));
} }
public toggle() { public toggle() {
this.next(s => ({ ...s, isOpen: !s.isOpen })); this.isCollapsed = !this.isCollapsed;
this.localStore.setBoolean(this.configKey(), !this.snapshot.isOpen); this.localStore.setBoolean(this.configKey(), this.isCollapsed);
} }
public ngOnChanges(changes: SimpleChanges): void { public ngOnChanges() {
if (changes['schemaCategory'] || changes['schemasFilter']) { this.filteredSchemas = this.schemaCategory.schemas;
let filtered = this.schemaCategory.schemas;
if (this.forContent) {
filtered = filtered.filter(x => x.canReadContents && x.isPublished);
}
let isOpen = false;
if (this.schemasFilter) { if (this.forContent) {
filtered = filtered.filter(x => x.name.indexOf(this.schemasFilter) >= 0); this.filteredSchemas = this.filteredSchemas.filter(x => x.canReadContents && x.isPublished);
}
isOpen = true; if (this.schemasFilter) {
} else { this.filteredSchemas = this.filteredSchemas.filter(x => x.name.indexOf(this.schemasFilter) >= 0);
isOpen = this.localStore.get(`schema-category.${this.schemaCategory.name}`) !== 'false';
}
this.next(s => ({ ...s, isOpen, filtered })); this.isCollapsed = false;
} else {
this.isCollapsed = this.localStore.getBoolean(this.configKey());
} }
} }
@ -92,11 +77,11 @@ export class SchemaCategoryComponent extends StatefulComponent<State> implements
} }
} }
public trackBySchema(index: number, schema: SchemaDto) { public trackBySchema(schema: SchemaDto) {
return schema.id; return schema.id;
} }
private configKey(): string { private configKey(): string {
return `squidex.schema.category.${this.schemaCategory.name}.closed`; return `squidex.schema.category.${this.schemaCategory.name}.collapsed`;
} }
} }

4
frontend/app/shared/services/schemas.service.ts

@ -285,10 +285,6 @@ export class RootFieldDto extends FieldDto {
return this.properties.fieldType === 'Array'; return this.properties.fieldType === 'Array';
} }
public get isTranslatable() {
return this.isLocalizable && this.properties.isTranslateable;
}
constructor(links: ResourceLinks, fieldId: number, name: string, properties: FieldPropertiesDto, constructor(links: ResourceLinks, fieldId: number, name: string, properties: FieldPropertiesDto,
public readonly partitioning: string, public readonly partitioning: string,
isLocked: boolean = false, isLocked: boolean = false,

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

@ -139,10 +139,6 @@ export abstract class FieldPropertiesDto {
public readonly placeholder?: string; public readonly placeholder?: string;
public readonly tags?: ReadonlyArray<string>; public readonly tags?: ReadonlyArray<string>;
public get isTranslateable() {
return false;
}
public get isComplexUI() { public get isComplexUI() {
return true; return true;
} }
@ -329,10 +325,6 @@ export class StringFieldPropertiesDto extends FieldPropertiesDto {
return this.editor !== 'Input' && this.editor !== 'Color' && this.editor !== 'Radio' && this.editor !== 'Slug' && this.editor !== 'TextArea'; return this.editor !== 'Input' && this.editor !== 'Color' && this.editor !== 'Radio' && this.editor !== 'Slug' && this.editor !== 'TextArea';
} }
public get isTranslateable() {
return this.editor === 'Input' || this.editor === 'TextArea';
}
public accept<T>(visitor: FieldPropertiesVisitor<T>): T { public accept<T>(visitor: FieldPropertiesVisitor<T>): T {
return visitor.visitString(this); return visitor.visitString(this);
} }

Loading…
Cancel
Save