mirror of https://github.com/Squidex/squidex.git
44 changed files with 1081 additions and 634 deletions
@ -0,0 +1,44 @@ |
|||||
|
/* |
||||
|
* Squidex Headless CMS |
||||
|
* |
||||
|
* @license |
||||
|
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
||||
|
*/ |
||||
|
|
||||
|
import { Injectable } from '@angular/core'; |
||||
|
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router'; |
||||
|
import { Observable } from 'rxjs'; |
||||
|
|
||||
|
import { allParams } from 'framework'; |
||||
|
|
||||
|
import { SchemasState } from './../state/schemas.state'; |
||||
|
|
||||
|
@Injectable() |
||||
|
export class SchemaMustExistGuard implements CanActivate { |
||||
|
constructor( |
||||
|
private readonly schemasState: SchemasState, |
||||
|
private readonly router: Router |
||||
|
) { |
||||
|
} |
||||
|
|
||||
|
public canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> { |
||||
|
const params = allParams(route); |
||||
|
|
||||
|
const schemaName = params['schemaName']; |
||||
|
|
||||
|
if (!schemaName) { |
||||
|
throw 'Route must contain schema name.'; |
||||
|
} |
||||
|
|
||||
|
const result = |
||||
|
this.schemasState.selectSchema(schemaName) |
||||
|
.do(dto => { |
||||
|
if (!dto) { |
||||
|
this.router.navigate(['/404']); |
||||
|
} |
||||
|
}) |
||||
|
.map(u => u !== null); |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,128 @@ |
|||||
|
<div class="modal-dialog modal-lg"> |
||||
|
<div class="modal-content" *ngIf="!editForm"> |
||||
|
<form [formGroup]="addFieldForm" (ngSubmit)="addField(false, false)"> |
||||
|
<div class="modal-header"> |
||||
|
<h4 class="modal-title"> |
||||
|
Create Field |
||||
|
</h4> |
||||
|
|
||||
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close" (click)="complete()"> |
||||
|
<span aria-hidden="true">×</span> |
||||
|
</button> |
||||
|
</div> |
||||
|
|
||||
|
<div class="modal-body"> |
||||
|
<h3 class="wizard-title">Create Field</h3> |
||||
|
|
||||
|
<div *ngIf="addFieldError"> |
||||
|
<div class="form-alert form-alert-error" [innerHTML]="addFieldError"></div> |
||||
|
</div> |
||||
|
|
||||
|
<div class="form-group"> |
||||
|
<div class="row"> |
||||
|
<div class="col-4 type" *ngFor="let fieldType of fieldTypes"> |
||||
|
<label> |
||||
|
<input type="radio" class="radio-input" formControlName="type" value="{{fieldType.type}}" /> |
||||
|
|
||||
|
<div class="row no-gutters"> |
||||
|
<div class="col col-auto"> |
||||
|
<div class="type-icon" [class.active]="addFieldForm.controls.type.value === fieldType.type"> |
||||
|
<i class="icon-type-{{fieldType.type}}"></i> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="col"> |
||||
|
<div class="type-title">{{fieldType.type}}</div> |
||||
|
<div class="type-text text-muted">{{fieldType.description}}</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
</label> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div class="form-group"> |
||||
|
<sqx-control-errors for="name" [submitted]="addFieldFormSubmitted"></sqx-control-errors> |
||||
|
|
||||
|
<input type="text" class="form-control" formControlName="name" maxlength="40" placeholder="Enter field name" sqxFocusOnInit /> |
||||
|
</div> |
||||
|
|
||||
|
<div class="form-group"> |
||||
|
<div class="form-check"> |
||||
|
<input class="form-check-input" type="checkbox" id="isLocalizable" formControlName="isLocalizable" /> |
||||
|
<label class="form-check-label" for="isLocalizable"> |
||||
|
Localizable |
||||
|
</label> |
||||
|
</div> |
||||
|
|
||||
|
<small class="form-text text-muted"> |
||||
|
You can the field as localizable. It means that is dependent on the language, for example a city name. |
||||
|
</small> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="modal-footer"> |
||||
|
<div class="clearfix"> |
||||
|
<button type="reset" class="float-left btn btn-secondary" (click)="complete()">Cancel</button> |
||||
|
|
||||
|
<div class="float-right"> |
||||
|
<button class="btn btn-success" (click)="addField(false, false)">Create and close</button> |
||||
|
<button class="btn btn-success" (click)="addField(true, false)">Create and new field</button> |
||||
|
<button class="btn btn-success" (click)="addField(false, true)">Create and configure</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</form> |
||||
|
</div> |
||||
|
|
||||
|
<div class="modal-content" *ngIf="editForm"> |
||||
|
<form [formGroup]="editForm"> |
||||
|
<div class="modal-header"> |
||||
|
|
||||
|
<h4 class="modal-title" *ngIf="editStep === 1"> |
||||
|
Step 1 of 3: Common Properties |
||||
|
</h4> |
||||
|
<h4 class="modal-title" *ngIf="editStep === 2"> |
||||
|
Step 2 of 3: Validation |
||||
|
</h4> |
||||
|
<h4 class="modal-title" *ngIf="editStep === 3"> |
||||
|
Step 3 of 3: Editing Settings |
||||
|
</h4> |
||||
|
|
||||
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close" (click)="complete()"> |
||||
|
<span aria-hidden="true">×</span> |
||||
|
</button> |
||||
|
</div> |
||||
|
|
||||
|
<div class="modal-body"> |
||||
|
<h3 class="wizard-title" *ngIf="editStep === 1">Common</h3> |
||||
|
<h3 class="wizard-title" *ngIf="editStep === 2">Validation</h3> |
||||
|
<h3 class="wizard-title" *ngIf="editStep === 3">Editing</h3> |
||||
|
|
||||
|
<div [class.hidden]="editStep !== 1"> |
||||
|
<sqx-field-form-common [editForm]="editForm" [editFormSubmitted]="editFormSubmitted" [showName]="false" [field]="editField"></sqx-field-form-common> |
||||
|
</div> |
||||
|
|
||||
|
<div [class.hidden]="editStep !== 2"> |
||||
|
<sqx-field-form-validation [editForm]="editForm" [field]="editField"></sqx-field-form-validation> |
||||
|
</div> |
||||
|
|
||||
|
<div [class.hidden]="editStep !== 3"> |
||||
|
<sqx-field-form-ui [editForm]="editForm" [field]="editField"></sqx-field-form-ui> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="modal-footer"> |
||||
|
<div class="clearfix"> |
||||
|
<button type="reset" class="float-left btn btn-secondary" (click)="complete()">Cancel</button> |
||||
|
|
||||
|
<div class="float-right" *ngIf="editStep !== 2"> |
||||
|
<button class="btn btn-primary" (click)="next()">Next</button> |
||||
|
</div> |
||||
|
<div class="float-right" *ngIf="editStep === 2"> |
||||
|
<button class="btn btn-success" (click)="configureField(false)">Configure and close</button> |
||||
|
<button class="btn btn-success" (click)="configureField(true)">Configure and new field</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</form> |
||||
|
</div> |
||||
|
</div> |
||||
@ -0,0 +1,65 @@ |
|||||
|
@import '_vars'; |
||||
|
@import '_mixins'; |
||||
|
|
||||
|
$icon-size: 4.5rem; |
||||
|
|
||||
|
.form-check { |
||||
|
margin-top: .4rem; |
||||
|
margin-bottom: -.2rem; |
||||
|
} |
||||
|
|
||||
|
.type { |
||||
|
& { |
||||
|
margin-bottom: .5rem; |
||||
|
} |
||||
|
|
||||
|
&-title { |
||||
|
font-weight: bold; |
||||
|
} |
||||
|
|
||||
|
&-text { |
||||
|
font-size: .9rem; |
||||
|
} |
||||
|
|
||||
|
&-icon { |
||||
|
& { |
||||
|
@include border-radius; |
||||
|
height: $icon-size; |
||||
|
color: $color-theme-blue; |
||||
|
cursor: pointer; |
||||
|
border: 1px solid $color-border; |
||||
|
background: transparent; |
||||
|
margin-right: .5rem; |
||||
|
line-height: $icon-size; |
||||
|
font-size: 1.75rem; |
||||
|
font-weight: normal; |
||||
|
text-align: center; |
||||
|
width: $icon-size; |
||||
|
} |
||||
|
|
||||
|
.radio-input { |
||||
|
display: none; |
||||
|
} |
||||
|
|
||||
|
&.active { |
||||
|
& { |
||||
|
@include box-shadow(0, 0, 10px, .5); |
||||
|
background: $color-theme-blue; |
||||
|
border-color: $color-theme-blue; |
||||
|
color: $color-dark-foreground; |
||||
|
} |
||||
|
|
||||
|
&:hover { |
||||
|
color: $color-dark-foreground; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
&:hover { |
||||
|
border-color: $color-border-dark; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.radio-input { |
||||
|
display: none; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,149 @@ |
|||||
|
/* |
||||
|
* Squidex Headless CMS |
||||
|
* |
||||
|
* @license |
||||
|
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
||||
|
*/ |
||||
|
|
||||
|
import { Component, EventEmitter, Input, Output } from '@angular/core'; |
||||
|
import { FormBuilder, Validators, FormGroup } from '@angular/forms'; |
||||
|
|
||||
|
import { |
||||
|
AddFieldDto, |
||||
|
AppContext, |
||||
|
createProperties, |
||||
|
fadeAnimation, |
||||
|
FieldDto, |
||||
|
fieldTypes, |
||||
|
SchemaDetailsDto, |
||||
|
UpdateFieldDto, |
||||
|
ValidatorsEx |
||||
|
} from 'shared'; |
||||
|
|
||||
|
import { SchemasState } from './../../state/schemas.state'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'sqx-field-wizard', |
||||
|
styleUrls: ['./field-wizard.component.scss'], |
||||
|
templateUrl: './field-wizard.component.html', |
||||
|
providers: [ |
||||
|
AppContext |
||||
|
], |
||||
|
animations: [ |
||||
|
fadeAnimation |
||||
|
] |
||||
|
}) |
||||
|
export class FieldWizardComponent { |
||||
|
public fieldTypes = fieldTypes; |
||||
|
|
||||
|
public editFormSubmitted = false; |
||||
|
public editStep = 0; |
||||
|
public editForm: FormGroup | null; |
||||
|
public editField: FieldDto | null; |
||||
|
|
||||
|
public addFieldError = ''; |
||||
|
public addFieldFormSubmitted = false; |
||||
|
public addFieldForm = |
||||
|
this.formBuilder.group({ |
||||
|
type: ['String', |
||||
|
[ |
||||
|
Validators.required |
||||
|
]], |
||||
|
name: ['', |
||||
|
[ |
||||
|
Validators.required, |
||||
|
Validators.maxLength(40), |
||||
|
ValidatorsEx.pattern('[a-zA-Z0-9]+(\\-[a-zA-Z0-9]+)*', 'Name must be a valid javascript name in camel case.') |
||||
|
]], |
||||
|
isLocalizable: [false] |
||||
|
}); |
||||
|
|
||||
|
@Input() |
||||
|
public schema: SchemaDetailsDto; |
||||
|
|
||||
|
@Output() |
||||
|
public completed = new EventEmitter(); |
||||
|
|
||||
|
constructor( |
||||
|
private readonly formBuilder: FormBuilder, |
||||
|
private readonly schemasState: SchemasState |
||||
|
) { |
||||
|
} |
||||
|
|
||||
|
public complete() { |
||||
|
this.completed.emit(); |
||||
|
} |
||||
|
|
||||
|
public next() { |
||||
|
this.editStep++; |
||||
|
} |
||||
|
|
||||
|
public addField(next: boolean, configure: boolean) { |
||||
|
this.addFieldFormSubmitted = true; |
||||
|
|
||||
|
if (this.addFieldForm.valid) { |
||||
|
this.addFieldForm.disable(); |
||||
|
|
||||
|
const properties = createProperties(this.addFieldForm.controls['type'].value); |
||||
|
|
||||
|
const partitioning = |
||||
|
this.addFieldForm.controls['isLocalizable'].value ? |
||||
|
'language' : |
||||
|
'invariant'; |
||||
|
|
||||
|
const requestDto = new AddFieldDto(this.addFieldForm.controls['name'].value, partitioning, properties); |
||||
|
|
||||
|
this.schemasState.addField(this.schema, requestDto) |
||||
|
.subscribe(dto => { |
||||
|
this.resetFieldForm(); |
||||
|
|
||||
|
if (configure) { |
||||
|
this.editField = dto; |
||||
|
this.editStep = 1; |
||||
|
this.editForm = new FormGroup({}); |
||||
|
} else if (!next) { |
||||
|
this.complete(); |
||||
|
} |
||||
|
}, error => { |
||||
|
this.resetFieldForm(error.displayMessage); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public configureField(next: boolean) { |
||||
|
this.editFormSubmitted = true; |
||||
|
|
||||
|
if (this.editForm!.valid) { |
||||
|
const properties = createProperties(this.editField!.properties['fieldType'], this.editForm!.value); |
||||
|
|
||||
|
this.schemasState.updateField(this.schema, this.editField!, new UpdateFieldDto(properties)) |
||||
|
.subscribe(() => { |
||||
|
this.resetEditForm(); |
||||
|
|
||||
|
if (this.next) { |
||||
|
this.editField = null; |
||||
|
this.editForm = null; |
||||
|
this.editStep = 1; |
||||
|
} else { |
||||
|
this.complete(); |
||||
|
} |
||||
|
}, error => { |
||||
|
this.resetEditForm(); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private resetEditForm() { |
||||
|
this.editForm!.enable(); |
||||
|
this.editForm!.reset(this.editField!.properties); |
||||
|
this.editFormSubmitted = false; |
||||
|
} |
||||
|
|
||||
|
private resetFieldForm(error = '') { |
||||
|
this.addFieldError = error; |
||||
|
this.addFieldForm.enable(); |
||||
|
this.addFieldForm.reset({ type: 'String' }); |
||||
|
this.addFieldFormSubmitted = false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
@ -0,0 +1,56 @@ |
|||||
|
<div [formGroup]="editForm"> |
||||
|
<div class="form-group row" *ngIf="showName"> |
||||
|
<label class="col col-3 col-form-label" for="fieldName">Name</label> |
||||
|
|
||||
|
<div class="col col-6"> |
||||
|
<input type="text" class="form-control" id="fieldName" readonly [ngModel]="field.name" [ngModelOptions]="{standalone: true}" /> |
||||
|
|
||||
|
<small class="form-text text-muted"> |
||||
|
The name of the field in the API response. |
||||
|
</small> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div class="form-group row"> |
||||
|
<label class="col col-3 col-form-label" for="fieldLabel">Label</label> |
||||
|
|
||||
|
<div class="col col-6"> |
||||
|
<sqx-control-errors for="label" [submitted]="editFormSubmitted"></sqx-control-errors> |
||||
|
|
||||
|
<input type="text" class="form-control" id="fieldLabel" maxlength="100" formControlName="label" /> |
||||
|
|
||||
|
<small class="form-text text-muted"> |
||||
|
Define the display name for the field for documentation and user interfaces. |
||||
|
</small> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div class="form-group row"> |
||||
|
<label class="col col-3 col-form-label" for="fieldHints">Hints</label> |
||||
|
|
||||
|
<div class="col col-6"> |
||||
|
<sqx-control-errors for="hints" [submitted]="editFormSubmitted"></sqx-control-errors> |
||||
|
|
||||
|
<input type="text" class="form-control" id="fieldHints" maxlength="100" formControlName="hints" /> |
||||
|
|
||||
|
<small class="form-text text-muted"> |
||||
|
Define some hints for the user and editor for the field for documentation and user interfaces. |
||||
|
</small> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div class="form-group row"> |
||||
|
<div class="col col-9 offset-3"> |
||||
|
<div class="form-check"> |
||||
|
<input class="form-check-input" type="checkbox" id="fieldListfield" formControlName="isListField" /> |
||||
|
<label class="form-check-label" for="fieldListfield"> |
||||
|
List Field |
||||
|
</label> |
||||
|
</div> |
||||
|
|
||||
|
<small class="form-text text-muted"> |
||||
|
List fields are shown as a column in the content list. If no list field is defined, the first field is shown by default. |
||||
|
</small> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
@ -0,0 +1,2 @@ |
|||||
|
@import '_vars'; |
||||
|
@import '_mixins'; |
||||
@ -0,0 +1,47 @@ |
|||||
|
/* |
||||
|
* Squidex Headless CMS |
||||
|
* |
||||
|
* @license |
||||
|
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
||||
|
*/ |
||||
|
|
||||
|
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core'; |
||||
|
import { FormControl, FormGroup, Validators } from '@angular/forms'; |
||||
|
|
||||
|
import { FieldDto } from 'shared'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'sqx-field-form-common', |
||||
|
styleUrls: ['field-form-common.component.scss'], |
||||
|
templateUrl: 'field-form-common.component.html', |
||||
|
changeDetection: ChangeDetectionStrategy.OnPush |
||||
|
}) |
||||
|
export class FieldFormCommonComponent implements OnInit { |
||||
|
@Input() |
||||
|
public editForm: FormGroup; |
||||
|
|
||||
|
@Input() |
||||
|
public editFormSubmitted = false; |
||||
|
|
||||
|
@Input() |
||||
|
public showName = true; |
||||
|
|
||||
|
@Input() |
||||
|
public field: FieldDto; |
||||
|
|
||||
|
public ngOnInit() { |
||||
|
this.editForm.setControl('label', |
||||
|
new FormControl(this.field.properties.label, |
||||
|
Validators.maxLength(100))); |
||||
|
|
||||
|
this.editForm.setControl('hints', |
||||
|
new FormControl(this.field.properties.label, |
||||
|
Validators.maxLength(100))); |
||||
|
|
||||
|
this.editForm.setControl('isRequired', |
||||
|
new FormControl(this.field.properties.isRequired)); |
||||
|
|
||||
|
this.editForm.setControl('isListField', |
||||
|
new FormControl(this.field.properties.isListField)); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,29 @@ |
|||||
|
<div [ngSwitch]="field.properties.fieldType"> |
||||
|
<div *ngSwitchCase="'Number'"> |
||||
|
<sqx-number-ui [editForm]="editForm" [properties]="field.properties"></sqx-number-ui> |
||||
|
</div> |
||||
|
<div *ngSwitchCase="'String'"> |
||||
|
<sqx-string-ui [editForm]="editForm" [properties]="field.properties"></sqx-string-ui> |
||||
|
</div> |
||||
|
<div *ngSwitchCase="'Boolean'"> |
||||
|
<sqx-boolean-ui [editForm]="editForm" [properties]="field.properties"></sqx-boolean-ui> |
||||
|
</div> |
||||
|
<div *ngSwitchCase="'DateTime'"> |
||||
|
<sqx-date-time-ui [editForm]="editForm" [properties]="field.properties"></sqx-date-time-ui> |
||||
|
</div> |
||||
|
<div *ngSwitchCase="'Geolocation'"> |
||||
|
<sqx-geolocation-ui [editForm]="editForm" [properties]="field.properties"></sqx-geolocation-ui> |
||||
|
</div> |
||||
|
<div *ngSwitchCase="'Json'"> |
||||
|
<sqx-json-ui [editForm]="editForm" [properties]="field.properties"></sqx-json-ui> |
||||
|
</div> |
||||
|
<div *ngSwitchCase="'Assets'"> |
||||
|
<sqx-assets-ui [editForm]="editForm" [properties]="field.properties"></sqx-assets-ui> |
||||
|
</div> |
||||
|
<div *ngSwitchCase="'References'"> |
||||
|
<sqx-references-ui [editForm]="editForm" [properties]="field.properties"></sqx-references-ui> |
||||
|
</div> |
||||
|
<div *ngSwitchCase="'Tags'"> |
||||
|
<sqx-tags-ui [editForm]="editForm" [properties]="field.properties"></sqx-tags-ui> |
||||
|
</div> |
||||
|
</div> |
||||
@ -0,0 +1,2 @@ |
|||||
|
@import '_vars'; |
||||
|
@import '_mixins'; |
||||
@ -0,0 +1,25 @@ |
|||||
|
/* |
||||
|
* Squidex Headless CMS |
||||
|
* |
||||
|
* @license |
||||
|
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
||||
|
*/ |
||||
|
|
||||
|
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; |
||||
|
import { FormGroup } from '@angular/forms'; |
||||
|
|
||||
|
import { FieldDto } from 'shared'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'sqx-field-form-ui', |
||||
|
styleUrls: ['field-form-ui.component.scss'], |
||||
|
templateUrl: 'field-form-ui.component.html', |
||||
|
changeDetection: ChangeDetectionStrategy.OnPush |
||||
|
}) |
||||
|
export class FieldFormUIComponent { |
||||
|
@Input() |
||||
|
public editForm: FormGroup; |
||||
|
|
||||
|
@Input() |
||||
|
public field: FieldDto; |
||||
|
} |
||||
@ -0,0 +1,29 @@ |
|||||
|
<div [ngSwitch]="field.properties.fieldType"> |
||||
|
<div *ngSwitchCase="'Number'"> |
||||
|
<sqx-number-validation [editForm]="editForm" [properties]="field.properties"></sqx-number-validation> |
||||
|
</div> |
||||
|
<div *ngSwitchCase="'String'"> |
||||
|
<sqx-string-validation [editForm]="editForm" [properties]="field.properties"></sqx-string-validation> |
||||
|
</div> |
||||
|
<div *ngSwitchCase="'Boolean'"> |
||||
|
<sqx-boolean-validation [editForm]="editForm" [properties]="field.properties"></sqx-boolean-validation> |
||||
|
</div> |
||||
|
<div *ngSwitchCase="'DateTime'"> |
||||
|
<sqx-date-time-validation [editForm]="editForm" [properties]="field.properties"></sqx-date-time-validation> |
||||
|
</div> |
||||
|
<div *ngSwitchCase="'Geolocation'"> |
||||
|
<sqx-geolocation-validation [editForm]="editForm" [properties]="field.properties"></sqx-geolocation-validation> |
||||
|
</div> |
||||
|
<div *ngSwitchCase="'Json'"> |
||||
|
<sqx-json-validation [editForm]="editForm" [properties]="field.properties"></sqx-json-validation> |
||||
|
</div> |
||||
|
<div *ngSwitchCase="'Assets'"> |
||||
|
<sqx-assets-validation [editForm]="editForm" [properties]="field.properties"></sqx-assets-validation> |
||||
|
</div> |
||||
|
<div *ngSwitchCase="'References'"> |
||||
|
<sqx-references-validation [editForm]="editForm" [properties]="field.properties"></sqx-references-validation> |
||||
|
</div> |
||||
|
<div *ngSwitchCase="'Tags'"> |
||||
|
<sqx-tags-validation [editForm]="editForm" [properties]="field.properties"></sqx-tags-validation> |
||||
|
</div> |
||||
|
</div> |
||||
@ -0,0 +1,2 @@ |
|||||
|
@import '_vars'; |
||||
|
@import '_mixins'; |
||||
@ -0,0 +1,25 @@ |
|||||
|
/* |
||||
|
* Squidex Headless CMS |
||||
|
* |
||||
|
* @license |
||||
|
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
||||
|
*/ |
||||
|
|
||||
|
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; |
||||
|
import { FormGroup } from '@angular/forms'; |
||||
|
|
||||
|
import { FieldDto } from 'shared'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'sqx-field-form-validation', |
||||
|
styleUrls: ['field-form-validation.component.scss'], |
||||
|
templateUrl: 'field-form-validation.component.html', |
||||
|
changeDetection: ChangeDetectionStrategy.OnPush |
||||
|
}) |
||||
|
export class FieldFormValidationComponent { |
||||
|
@Input() |
||||
|
public editForm: FormGroup; |
||||
|
|
||||
|
@Input() |
||||
|
public field: FieldDto; |
||||
|
} |
||||
@ -0,0 +1,201 @@ |
|||||
|
/* |
||||
|
* Squidex Headless CMS |
||||
|
* |
||||
|
* @license |
||||
|
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
||||
|
*/ |
||||
|
|
||||
|
import { Injectable } from '@angular/core'; |
||||
|
import { BehaviorSubject, Observable } from 'rxjs'; |
||||
|
|
||||
|
import 'framework/utils/rxjs-extensions'; |
||||
|
|
||||
|
import { |
||||
|
AddFieldDto, |
||||
|
AppsStoreService, |
||||
|
AuthService, |
||||
|
CreateSchemaDto, |
||||
|
DateTime, |
||||
|
DialogService, |
||||
|
ErrorDto, |
||||
|
FieldDto, |
||||
|
ImmutableArray, |
||||
|
Notification, |
||||
|
SchemaDto, |
||||
|
SchemaDetailsDto, |
||||
|
SchemasService, |
||||
|
UpdateFieldDto |
||||
|
} from 'shared'; |
||||
|
|
||||
|
@Injectable() |
||||
|
export class SchemasState { |
||||
|
public schemasItems = new BehaviorSubject<ImmutableArray<SchemaDto>>(ImmutableArray.empty()); |
||||
|
|
||||
|
public selectedSchema = new BehaviorSubject<SchemaDetailsDto | null>(null); |
||||
|
|
||||
|
private get app() { |
||||
|
return this.appsState.app$.value!.name; |
||||
|
} |
||||
|
|
||||
|
private get user() { |
||||
|
return this.authState.user!.token; |
||||
|
} |
||||
|
|
||||
|
constructor( |
||||
|
private readonly schemasService: SchemasService, |
||||
|
private readonly dialogs: DialogService, |
||||
|
private readonly authState: AuthService, |
||||
|
private readonly appsState: AppsStoreService |
||||
|
) { |
||||
|
} |
||||
|
|
||||
|
public selectSchema(id: string | null): Observable<boolean> { |
||||
|
const observable = |
||||
|
!id ? |
||||
|
Observable.of(null) : |
||||
|
Observable.of(<SchemaDetailsDto>this.schemasItems.value.find(x => x.id === id && x instanceof SchemaDetailsDto)) |
||||
|
.switchMap(schema => { |
||||
|
if (!schema) { |
||||
|
return this.schemasService.getSchema(this.appsState.app$.value!.name, id).catch(() => Observable.of(null)); |
||||
|
} else { |
||||
|
return Observable.of(schema); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
return observable |
||||
|
.do(schema => { |
||||
|
this.selectedSchema.next(schema); |
||||
|
}) |
||||
|
.map(s => s !== null); |
||||
|
} |
||||
|
|
||||
|
public load(): Observable<any> { |
||||
|
return this.schemasService.getSchemas(this.app) |
||||
|
.catch(error => this.notifyError(error)) |
||||
|
.do(dtos => { |
||||
|
this.schemasItems.nextBy(v => ImmutableArray.of(dtos)); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public create(request: CreateSchemaDto) { |
||||
|
return this.schemasService.postSchema(this.app, request, this.user, DateTime.now()) |
||||
|
.do(dto => { |
||||
|
this.schemasItems.nextBy(v => v.push(dto)); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public addField(schema: SchemaDetailsDto, request: AddFieldDto): Observable<FieldDto> { |
||||
|
return this.schemasService.postField(this.app, schema.name, request, schema.version) |
||||
|
.do(dto => { |
||||
|
this.replaceSchema(schema.addField(dto.payload, this.user, dto.version)); |
||||
|
}).map(d => d.payload); |
||||
|
} |
||||
|
|
||||
|
public publish(schema: SchemaDto): Observable<any> { |
||||
|
return this.schemasService.publishSchema(this.app, schema.name, schema.version) |
||||
|
.catch(error => this.notifyError(error)) |
||||
|
.do(dto => { |
||||
|
this.replaceSchema(schema.publish(this.user, dto.version)); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public unpublish(schema: SchemaDto): Observable<any> { |
||||
|
return this.schemasService.unpublishSchema(this.app, schema.name, schema.version) |
||||
|
.catch(error => this.notifyError(error)) |
||||
|
.do(dto => { |
||||
|
this.replaceSchema(schema.unpublish(this.user, dto.version)); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public enableField(schema: SchemaDetailsDto, field: FieldDto): Observable<any> { |
||||
|
return this.schemasService.enableField(this.app, schema.name, field.fieldId, schema.version) |
||||
|
.catch(error => this.notifyError(error)) |
||||
|
.do(dto => { |
||||
|
this.replaceSchema(schema.updateField(field.enable(), this.user, dto.version)); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public disableField(schema: SchemaDetailsDto, field: FieldDto): Observable<any> { |
||||
|
return this.schemasService.disableField(this.app, schema.name, field.fieldId, schema.version) |
||||
|
.catch(error => this.notifyError(error)) |
||||
|
.do(dto => { |
||||
|
this.replaceSchema(schema.updateField(field.disable(), this.user, dto.version)); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public lockField(schema: SchemaDetailsDto, field: FieldDto): Observable<any> { |
||||
|
return this.schemasService.lockField(this.app, schema.name, field.fieldId, schema.version) |
||||
|
.catch(error => this.notifyError(error)) |
||||
|
.do(dto => { |
||||
|
this.replaceSchema(schema.updateField(field.lock(), this.user, dto.version)); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public showField(schema: SchemaDetailsDto, field: FieldDto): Observable<any> { |
||||
|
return this.schemasService.showField(this.app, schema.name, field.fieldId, schema.version) |
||||
|
.catch(error => this.notifyError(error)) |
||||
|
.do(dto => { |
||||
|
this.replaceSchema(schema.updateField(field.show(), this.user, dto.version)); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public hideField(schema: SchemaDetailsDto, field: FieldDto): Observable<any> { |
||||
|
return this.schemasService.hideField(this.app, schema.name, field.fieldId, schema.version) |
||||
|
.catch(error => this.notifyError(error)) |
||||
|
.do(dto => { |
||||
|
this.replaceSchema(schema.updateField(field.hide(), this.user, dto.version)); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public deleteField(schema: SchemaDetailsDto, field: FieldDto): Observable<any> { |
||||
|
return this.schemasService.deleteField(this.app, schema.name, field.fieldId, schema.version) |
||||
|
.catch(error => this.notifyError(error)) |
||||
|
.do(dto => { |
||||
|
this.replaceSchema(schema.removeField(field, this.user, dto.version)); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public sortFields(schema: SchemaDetailsDto, fields: FieldDto[]): Observable<any> { |
||||
|
return this.schemasService.putFieldOrdering(this.app, schema.name, fields.map(t => t.fieldId), schema.version) |
||||
|
.catch(error => this.notifyError(error)) |
||||
|
.do(dto => { |
||||
|
this.replaceSchema(schema.replaceFields(fields, this.user, dto.version)); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public updateField(schema: SchemaDetailsDto, field: FieldDto, request: UpdateFieldDto): Observable<any> { |
||||
|
return this.schemasService.putField(this.app, schema.name, field.fieldId, request, schema.version) |
||||
|
.catch(error => this.notifyError(error)) |
||||
|
.do(dto => { |
||||
|
this.replaceSchema(schema.updateField(field.update(request.properties), this.user, dto.version)); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public delete(schema: SchemaDto): Observable<any> { |
||||
|
return this.schemasService.deleteSchema(this.app, schema.name, schema.version) |
||||
|
.catch(error => this.notifyError(error)) |
||||
|
.do(dto => { |
||||
|
this.schemasItems.nextBy(v => v.filter(s => s.id !== schema.id)); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private replaceSchema(schema: SchemaDto) { |
||||
|
this.schemasItems.nextBy(v => v.replaceBy('id', schema)); |
||||
|
|
||||
|
this.selectedSchema.nextBy(v => v !== null && v.id === schema.id ? <SchemaDetailsDto>schema : v); |
||||
|
} |
||||
|
|
||||
|
public trackBy(index: number, schema: SchemaDto): any { |
||||
|
return schema.id; |
||||
|
} |
||||
|
|
||||
|
private notifyError(error: string | ErrorDto) { |
||||
|
if (error instanceof ErrorDto) { |
||||
|
this.dialogs.notify(Notification.error(error.displayMessage)); |
||||
|
} else { |
||||
|
this.dialogs.notify(Notification.error(error)); |
||||
|
} |
||||
|
|
||||
|
return Observable.throw(error); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,65 @@ |
|||||
|
|
||||
|
/* |
||||
|
* Squidex Headless CMS |
||||
|
* |
||||
|
* @license |
||||
|
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
||||
|
*/ |
||||
|
|
||||
|
import 'framework/utils/rxjs-extensions'; |
||||
|
|
||||
|
import { Injectable } from '@angular/core'; |
||||
|
import { BehaviorSubject, Observable } from 'rxjs'; |
||||
|
|
||||
|
import { DateTime, ImmutableArray } from 'framework'; |
||||
|
|
||||
|
import { |
||||
|
AppDto, |
||||
|
AppsService, |
||||
|
CreateAppDto |
||||
|
} from './../services/apps.service'; |
||||
|
|
||||
|
@Injectable() |
||||
|
export class AppsState { |
||||
|
public apps = new BehaviorSubject<ImmutableArray<AppDto>>(ImmutableArray.empty()); |
||||
|
|
||||
|
public selectedApp = new BehaviorSubject<AppDto | null>(null); |
||||
|
|
||||
|
constructor( |
||||
|
private readonly appsService: AppsService |
||||
|
) { |
||||
|
} |
||||
|
|
||||
|
public loadApps(): Observable<any> { |
||||
|
return this.appsService.getApps() |
||||
|
.do(dtos => { |
||||
|
this.apps.nextBy(v => ImmutableArray.of(dtos)); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public selectApp(name: string | null): Observable<AppDto | null> { |
||||
|
const observable = |
||||
|
!name ? |
||||
|
Observable.of(null) : |
||||
|
Observable.of(this.apps.value.find(x => x.name === name) || null); |
||||
|
|
||||
|
return observable |
||||
|
.do(app => { |
||||
|
this.selectedApp.next(app); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public createApp(dto: CreateAppDto, now?: DateTime): Observable<AppDto> { |
||||
|
return this.appsService.postApp(dto) |
||||
|
.do(app => { |
||||
|
this.apps.nextBy(v => v.push(app)); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public deleteApp(name: string): Observable<any> { |
||||
|
return this.appsService.deleteApp(name) |
||||
|
.do(app => { |
||||
|
this.apps.nextBy(v => v.filter(a => a.name !== name)); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
After Width: | Height: | Size: 909 B |
Loading…
Reference in new issue