Browse Source

Started with array editor.

pull/297/head
Sebastian 8 years ago
parent
commit
e699ceb4c5
  1. 2
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Extensions.cs
  2. 8
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository_SnapshotStore.cs
  3. 1
      src/Squidex/app/features/content/declarations.ts
  4. 2
      src/Squidex/app/features/content/module.ts
  5. 11
      src/Squidex/app/features/content/pages/content/content-field.component.html
  6. 18
      src/Squidex/app/features/content/pages/content/content-field.component.ts
  7. 6
      src/Squidex/app/features/content/pages/content/content-page.component.html
  8. 14
      src/Squidex/app/features/content/pages/schemas/schemas-page.component.html
  9. 133
      src/Squidex/app/features/content/shared/array-editor.component.html
  10. 28
      src/Squidex/app/features/content/shared/array-editor.component.scss
  11. 52
      src/Squidex/app/features/content/shared/array-editor.component.ts
  12. 2
      src/Squidex/app/shared/components/schema-category.component.html
  13. 84
      src/Squidex/app/shared/state/contents.state.ts

2
src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Extensions.cs

@ -42,7 +42,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
return result.ConvertName2Id(schema,
FieldConverters.ForValues(
ValueConverters.EncodeJson()),
FieldConverters.ForNestedId2Name(
FieldConverters.ForNestedName2Id(
ValueConverters.EncodeJson()));
}

8
src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository_SnapshotStore.cs

@ -39,11 +39,17 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
var schema = await GetSchemaAsync(value.AppId.Id, value.SchemaId.Id);
var idData = value.Data.ToMongoModel(schema.SchemaDef);
var idDraftData = idData;
if (!ReferenceEquals(value.Data, value.DataDraft))
{
idDraftData = value.DataDraft?.ToMongoModel(schema.SchemaDef);
}
var content = SimpleMapper.Map(value, new MongoContentEntity
{
DataByIds = idData,
DataDraftByIds = value.DataDraft?.ToMongoModel(schema.SchemaDef),
DataDraftByIds = idDraftData,
IsDeleted = value.IsDeleted,
IndexedAppId = value.AppId.Id,
IndexedSchemaId = value.SchemaId.Id,

1
src/Squidex/app/features/content/declarations.ts

@ -12,6 +12,7 @@ export * from './pages/contents/contents-page.component';
export * from './pages/contents/search-form.component';
export * from './pages/schemas/schemas-page.component';
export * from './shared/array-editor.component';
export * from './shared/assets-editor.component';
export * from './shared/content-item.component';
export * from './shared/content-status.component';

2
src/Squidex/app/features/content/module.ts

@ -19,6 +19,7 @@ import {
} from '@app/shared';
import {
ArrayEditorComponent,
AssetsEditorComponent,
ContentFieldComponent,
ContentHistoryComponent,
@ -84,6 +85,7 @@ const routes: Routes = [
RouterModule.forChild(routes)
],
declarations: [
ArrayEditorComponent,
AssetsEditorComponent,
ContentFieldComponent,
ContentHistoryComponent,

11
src/Squidex/app/features/content/pages/content/content-field.component.html

@ -19,7 +19,7 @@
</sqx-onboarding-tooltip>
</ng-container>
<sqx-control-errors [for]="selectedFormControl" [fieldName]="field.displayName" [submitted]="contentFormSubmitted"></sqx-control-errors>
<sqx-control-errors [for]="selectedFormControl" [fieldName]="field.displayName" [submitted]="form.submitted | async"></sqx-control-errors>
<div>
<ng-container *ngIf="field.properties.editorUrl">
@ -110,6 +110,15 @@
<ng-container *ngSwitchCase="'Tags'">
<sqx-tag-editor [formControl]="selectedFormControl"></sqx-tag-editor>
</ng-container>
<ng-container *ngSwitchCase="'Array'">
<sqx-array-editor
[arrayControl]="selectedFormControl"
[form]="form"
[field]="field"
[language]="language"
[languages]="languages">
</sqx-array-editor>
</ng-container>
<ng-container *ngSwitchCase="'References'">
<sqx-references-editor
[formControl]="selectedFormControl"

18
src/Squidex/app/features/content/pages/content/content-field.component.ts

@ -10,9 +10,11 @@ import { AbstractControl, FormGroup } from '@angular/forms';
import {
AppLanguageDto,
EditContentForm,
fieldInvariant,
ImmutableArray,
RootFieldDto
RootFieldDto,
Types
} from '@app/shared';
@Component({
@ -21,6 +23,9 @@ import {
templateUrl: './content-field.component.html'
})
export class ContentFieldComponent implements OnChanges {
@Input()
public form: EditContentForm;
@Input()
public field: RootFieldDto;
@ -30,14 +35,11 @@ export class ContentFieldComponent implements OnChanges {
@Input()
public language: AppLanguageDto;
@Output()
public languageChange = new EventEmitter<AppLanguageDto>();
@Input()
public languages: ImmutableArray<AppLanguageDto>;
@Input()
public contentFormSubmitted: boolean;
@Output()
public languageChange = new EventEmitter<AppLanguageDto>();
public selectedFormControl: AbstractControl;
@ -49,7 +51,9 @@ export class ContentFieldComponent implements OnChanges {
}
if (changes['language']) {
this.selectedFormControl['_clearChangeFns']();
if (Types.isFunction(this.selectedFormControl['_clearChangeFns'])) {
this.selectedFormControl['_clearChangeFns']();
}
}
}
}

6
src/Squidex/app/features/content/pages/content/content-page.component.html

@ -104,11 +104,11 @@
<div *ngFor="let field of schema.fields">
<sqx-content-field
[form]="contentForm"
[field]="field"
[fieldForm]="contentForm.form.controls[field.name]"
[fieldForm]="contentForm.form.get(field.name)"
[(language)]="language"
[languages]="languages"
[contentFormSubmitted]="contentForm.submitted | async">
[languages]="languages">
</sqx-content-field>
</div>
</ng-container>

14
src/Squidex/app/features/content/pages/schemas/schemas-page.component.html

@ -16,12 +16,14 @@
</ng-container>
<ng-container content>
<sqx-schema-category *ngFor="let category of schemasState.categories | async; trackBy: trackByCategory"
[name]="category"
[schemas]="schemasState.schemas | async"
[schemasFilter]="schemasFilter.valueChanges | async"
[isReadonly]="true">
</sqx-schema-category>
<ng-container *ngIf="schemasState.publishedSchemas | async; let schemas">
<sqx-schema-category *ngFor="let category of schemasState.categories | async; trackBy: trackByCategory"
[name]="category"
[schemas]="schemas"
[schemasFilter]="schemasFilter.valueChanges | async"
[isReadonly]="true">
</sqx-schema-category>
</ng-container>
</ng-container>
</sqx-panel>

133
src/Squidex/app/features/content/shared/array-editor.component.html

@ -0,0 +1,133 @@
<div class="array-container" *ngIf="arrayControl.controls.length > 0">
<div class="array-item" *ngFor="let nestedForm of arrayControl.controls; let i = index">
<button type="button" class="btn btn-link btn-danger array-item-remove" (click)="removeItem(i)">
<i class="icon-bin2"></i>
</button>
<div class="form-group" *ngFor="let nestedField of field.nested">
<ng-container *ngIf="nestedForm.get(nestedField.name); let nestedFieldForm">
<label>
{{nestedField.displayName}} <span class="field-required" [class.hidden]="!nestedField.properties.isRequired">*</span>
</label>
<span class="field-disabled" *ngIf="nestedField.isDisabled">Disabled</span>
<sqx-control-errors [for]="nestedFieldForm" [fieldName]="nestedField.displayName" [submitted]="form.submitted | async"></sqx-control-errors>
<div>
<ng-container *ngIf="nestedField.properties.editorUrl">
<sqx-iframe-editor [url]="nestedField.properties.editorUrl" [formControl]="nestedFieldForm"></sqx-iframe-editor>
</ng-container>
<ng-container *ngIf="!nestedField.properties.editorUrl">
<ng-container [ngSwitch]="nestedField.properties.fieldType">
<ng-container *ngSwitchCase="'Number'">
<ng-container [ngSwitch]="nestedField.properties['editor']">
<ng-container *ngSwitchCase="'Input'">
<input class="form-control" type="number" [formControl]="nestedFieldForm" [placeholder]="nestedField.displayPlaceholder" />
</ng-container>
<ng-container *ngSwitchCase="'Stars'">
<sqx-stars [formControl]="nestedFieldForm" [maximumStars]="nestedField.properties['maxValue']"></sqx-stars>
</ng-container>
<ng-container *ngSwitchCase="'Dropdown'">
<select class="form-control" [formControl]="nestedFieldForm">
<option [ngValue]="null"></option>
<option *ngFor="let value of nestedField.properties['allowedValues']" [ngValue]="value">{{value}}</option>
</select>
</ng-container>
<ng-container *ngSwitchCase="'Radio'">
<ng-container class="form-check form-check-inline" *ngFor="let value of nestedField.properties['allowedValues']">
<input class="form-check-input" type="radio" [value]="value" [formControl]="nestedFieldForm" />
<label class="form-check-label">
{{value}}
</label>
</ng-container>
</ng-container>
</ng-container>
</ng-container>
<ng-container *ngSwitchCase="'String'">
<ng-container [ngSwitch]="nestedField.properties['editor']">
<ng-container *ngSwitchCase="'Input'">
<input class="form-control" type="text" [formControl]="nestedFieldForm" [placeholder]="nestedField.displayPlaceholder" />
</ng-container>
<ng-container *ngSwitchCase="'Slug'">
<input class="form-control" type="text" [formControl]="nestedFieldForm" [placeholder]="nestedField.displayPlaceholder" sqxTransformInput="Slugify" />
</ng-container>
<ng-container *ngSwitchCase="'TextArea'">
<textarea class="form-control" [formControl]="nestedFieldForm" rows="5" [placeholder]="nestedField.displayPlaceholder"></textarea>
</ng-container>
<ng-container *ngSwitchCase="'RichText'">
<sqx-rich-editor [formControl]="nestedFieldForm"></sqx-rich-editor>
</ng-container>
<ng-container *ngSwitchCase="'Markdown'">
<sqx-markdown-editor [formControl]="nestedFieldForm"></sqx-markdown-editor>
</ng-container>
<ng-container *ngSwitchCase="'Dropdown'">
<select class="form-control" [formControl]="nestedFieldForm">
<option [ngValue]="null"></option>
<option *ngFor="let value of nestedField.properties['allowedValues']" [ngValue]="value">{{value}}</option>
</select>
</ng-container>
<ng-container *ngSwitchCase="'Radio'">
<ng-container class="form-check form-check-inline" *ngFor="let value of nestedField.properties['allowedValues']">
<input class="form-check-input" type="radio" value="{{value}}" [formControl]="nestedFieldForm" />
<label class="form-check-label">
{{value}}
</label>
</ng-container>
</ng-container>
</ng-container>
</ng-container>
<ng-container *ngSwitchCase="'Boolean'">
<ng-container [ngSwitch]="nestedField.properties['editor']">
<ng-container *ngSwitchCase="'Toggle'">
<sqx-toggle [formControl]="nestedFieldForm"></sqx-toggle>
</ng-container>
<ng-container *ngSwitchCase="'Checkbox'">
<input type="checkbox" [formControl]="nestedFieldForm" sqxIndeterminateValue />
</ng-container>
</ng-container>
</ng-container>
<ng-container *ngSwitchCase="'DateTime'">
<sqx-date-time-editor enforceTime="true" [mode]="nestedField.properties['editor']" [formControl]="nestedFieldForm"></sqx-date-time-editor>
</ng-container>
<ng-container *ngSwitchCase="'Geolocation'">
<sqx-geolocation-editor [formControl]="nestedFieldForm"></sqx-geolocation-editor>
</ng-container>
<ng-container *ngSwitchCase="'Json'">
<sqx-json-editor [formControl]="nestedFieldForm"></sqx-json-editor>
</ng-container>
<ng-container *ngSwitchCase="'Assets'">
<sqx-assets-editor [formControl]="nestedFieldForm"></sqx-assets-editor>
</ng-container>
<ng-container *ngSwitchCase="'Tags'">
<sqx-tag-editor [formControl]="nestedFieldForm"></sqx-tag-editor>
</ng-container>
<ng-container *ngSwitchCase="'Array'">
<sqx-array-editor [formControl]="nestedFieldForm" [field]="field"></sqx-array-editor>
</ng-container>
<ng-container *ngSwitchCase="'References'">
<sqx-references-editor
[formControl]="nestedFieldForm"
[language]="language"
[languages]="languages"
[schemaId]="nestedField.properties['schemaId']">
</sqx-references-editor>
</ng-container>
</ng-container>
</ng-container>
</div>
<ng-container *ngIf="nestedField.properties['hints']; let hints">
<small class="form-text text-muted" *ngIf="hints.length > 0">
{{hints}}
</small>
</ng-container>
</ng-container>
</div>
</div>
</div>
<button class="btn btn-success" (click)="addItem()">
Add Item
</button>

28
src/Squidex/app/features/content/shared/array-editor.component.scss

@ -0,0 +1,28 @@
@import '_vars';
@import '_mixins';
.array {
&-container {
background: $color-border;
padding: 1rem;
position: relative;
margin-bottom: 1rem;
}
&-item {
& {
background: $panel-light-background;
padding: 1rem;
position: relative;
margin-bottom: 1rem;
}
&:last-child {
margin-bottom: 0;
}
}
&-item-remove {
@include absolute(.5rem, .5rem, auto, auto);
}
}

52
src/Squidex/app/features/content/shared/array-editor.component.ts

@ -0,0 +1,52 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
// tslint:disable:prefer-for-of
import { Component, Input } from '@angular/core';
import { FormArray } from '@angular/forms';
import {
AppLanguageDto,
EditContentForm,
ImmutableArray,
RootFieldDto
} from '@app/shared';
@Component({
selector: 'sqx-array-editor',
styleUrls: ['./array-editor.component.scss'],
templateUrl: './array-editor.component.html'
})
export class ArrayEditorComponent {
@Input()
public form: EditContentForm;
@Input()
public field: RootFieldDto;
@Input()
public language: AppLanguageDto;
@Input()
public languages: ImmutableArray<AppLanguageDto>;
@Input()
public arrayControl: FormArray;
public removeItem(index: number) {
this.form.removeArrayItem(this.field, this.language, index);
return false;
}
public addItem() {
this.form.insertArrayItem(this.field, this.language);
return false;
}
}

2
src/Squidex/app/shared/components/schema-category.component.html

@ -1,4 +1,4 @@
<div dnd-droppable class="droppable category" [allowDrop]="allowDrop" (onDropSuccess)="changeCategory($event.dragData)">
<div *ngIf="!isReadonly || (schemasForCategory && schemasForCategory.length > 0)" dnd-droppable class="droppable category" [allowDrop]="allowDrop" (onDropSuccess)="changeCategory($event.dragData)">
<div class="drop-indicator"></div>
<div class="header clearfix">

84
src/Squidex/app/shared/state/contents.state.ts

@ -5,8 +5,10 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
// tslint:disable:prefer-for-of
import { Injectable } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';
import { FormArray, FormControl, FormGroup } from '@angular/forms';
import { Observable } from 'rxjs';
import '@app/framework/utils/rxjs-extensions';
@ -19,13 +21,14 @@ import {
ImmutableArray,
Pager,
State,
Types,
Version,
Versioned
} from '@app/framework';
import { AppLanguageDto } from './../services/app-languages.service';
import { AuthService } from './../services/auth.service';
import { fieldInvariant, SchemaDetailsDto, SchemaDto } from './../services/schemas.service';
import { fieldInvariant, RootFieldDto, SchemaDetailsDto, SchemaDto } from './../services/schemas.service';
import { AppsState } from './apps.state';
import { SchemasState } from './schemas.state';
@ -40,15 +43,22 @@ export class EditContentForm extends Form<FormGroup> {
for (const field of schema.fields) {
const fieldForm = new FormGroup({});
const fieldDefault = field.defaultValue();
const defaultValue = field.defaultValue();
const createControl = (isOptional: boolean) => {
if (field.properties.fieldType === 'Array') {
return new FormArray([], field.createValidators(isOptional));
} else {
return new FormControl(fieldDefault, field.createValidators(isOptional));
}
};
if (field.isLocalizable) {
for (let language of this.languages.values) {
fieldForm.setControl(language.iso2Code, new FormControl(defaultValue, field.createValidators(language.isOptional)));
fieldForm.setControl(language.iso2Code, createControl(language.isOptional));
}
} else {
fieldForm.setControl(fieldInvariant, new FormControl(defaultValue, field.createValidators(false)));
fieldForm.setControl(fieldInvariant, createControl(false));
}
this.form.setControl(field.name, fieldForm);
@ -57,6 +67,42 @@ export class EditContentForm extends Form<FormGroup> {
this.enableContentForm();
}
public removeArrayItem(field: RootFieldDto, language: AppLanguageDto, index: number) {
this.findArrayItemForm(field, language).removeAt(index);
}
public insertArrayItem(field: RootFieldDto, language: AppLanguageDto) {
if (field.nested.length > 0) {
const formControl = this.findArrayItemForm(field, language);
this.addArrayItem(field, language, formControl);
}
}
private addArrayItem(field: RootFieldDto, language: AppLanguageDto | null, formControl: FormArray) {
const formItem = new FormGroup({});
let isOptional = field.isLocalizable && language !== null && language.isOptional;
for (let nested of field.nested) {
const nestedDefault = field.defaultValue();
formItem.setControl(nested.name, new FormControl(nestedDefault, nested.createValidators(isOptional)));
}
formControl.push(formItem);
}
private findArrayItemForm(field: RootFieldDto, language: AppLanguageDto): FormArray {
const fieldForm = this.form.get(field.name)!;
if (field.isLocalizable) {
return <FormArray>fieldForm.get(language.iso2Code)!;
} else {
return <FormArray>fieldForm.get(fieldInvariant);
}
}
public submitCompleted(newValue?: any) {
super.submitCompleted(newValue);
@ -70,6 +116,34 @@ export class EditContentForm extends Form<FormGroup> {
}
public loadData(value: any, isArchive: boolean) {
for (let field of this.schema.fields) {
if (field.properties.fieldType === 'Array' && field.nested.length > 0) {
const fieldValue = value ? value[field.name] || {} : {};
const fieldForm = <FormGroup>this.form.get(field.name)!;
const addControls = (key: string, language: AppLanguageDto | null) => {
const languageValue = fieldValue[key];
const languageForm = new FormArray([]);
if (Types.isArray(languageValue)) {
for (let i = 0; i < languageValue.length; i++) {
this.addArrayItem(field, language, languageForm);
}
}
fieldForm.setControl(key, languageForm);
};
if (field.isLocalizable) {
for (let language of this.languages.values) {
addControls(language.iso2Code, language);
}
} else {
addControls(fieldInvariant, null);
}
}
}
super.load(value);
if (isArchive) {

Loading…
Cancel
Save