Browse Source

Editor SDK.

pull/286/head
Sebastian 8 years ago
parent
commit
015bd3b3a2
  1. 2
      src/Squidex.Domain.Apps.Core.Model/Schemas/FieldProperties.cs
  2. 5
      src/Squidex/Areas/Api/Controllers/Schemas/Models/FieldPropertiesDto.cs
  3. 194
      src/Squidex/app/features/content/pages/content/content-field.component.html
  4. 11
      src/Squidex/app/features/schemas/pages/schema/types/boolean-ui.component.html
  5. 5
      src/Squidex/app/features/schemas/pages/schema/types/boolean-ui.component.ts
  6. 11
      src/Squidex/app/features/schemas/pages/schema/types/date-time-ui.component.html
  7. 5
      src/Squidex/app/features/schemas/pages/schema/types/date-time-ui.component.ts
  8. 11
      src/Squidex/app/features/schemas/pages/schema/types/geolocation-ui.component.html
  9. 11
      src/Squidex/app/features/schemas/pages/schema/types/number-ui.component.html
  10. 5
      src/Squidex/app/features/schemas/pages/schema/types/number-ui.component.ts
  11. 11
      src/Squidex/app/features/schemas/pages/schema/types/string-ui.component.html
  12. 5
      src/Squidex/app/features/schemas/pages/schema/types/string-ui.component.ts
  13. 1
      src/Squidex/app/framework/angular/forms/iframe-editor.component.html
  14. 8
      src/Squidex/app/framework/angular/forms/iframe-editor.component.scss
  15. 111
      src/Squidex/app/framework/angular/forms/iframe-editor.component.ts
  16. 1
      src/Squidex/app/framework/declarations.ts
  17. 3
      src/Squidex/app/framework/module.ts
  18. 48
      src/Squidex/app/shared/services/schemas.fields.spec.ts
  19. 55
      src/Squidex/app/shared/services/schemas.service.ts
  20. 6
      src/Squidex/app/shared/state/schemas.state.ts
  21. 36
      tools/Migrate_01/Migrations/PopulateGrainIndexes.cs

2
src/Squidex.Domain.Apps.Core.Model/Schemas/FieldProperties.cs

@ -15,6 +15,8 @@ namespace Squidex.Domain.Apps.Core.Schemas
public string Placeholder { get; set; }
public string EditorUrl { get; set; }
public abstract T Accept<T>(IFieldPropertiesVisitor<T> visitor);
public abstract Field CreateField(long id, string name, Partitioning partitioning);

5
src/Squidex/Areas/Api/Controllers/Schemas/Models/FieldPropertiesDto.cs

@ -46,6 +46,11 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models
/// </summary>
public bool IsListField { get; set; }
/// <summary>
/// Optional url to the editor.
/// </summary>
public string EditorUrl { get; set; }
/// <summary>
/// Gets the partitioning of the language, e.g. invariant or language.
/// </summary>

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

@ -21,99 +21,107 @@
<sqx-control-errors [for]="selectedFormControl" [fieldName]="field.displayName" [submitted]="contentFormSubmitted"></sqx-control-errors>
<div [ngSwitch]="field.properties.fieldType">
<div *ngSwitchCase="'Number'">
<div [ngSwitch]="field.properties['editor']">
<div *ngSwitchCase="'Input'">
<input class="form-control" type="number" [formControl]="selectedFormControl" [placeholder]="field.displayPlaceholder" />
</div>
<div *ngSwitchCase="'Stars'">
<sqx-stars [formControl]="selectedFormControl" [maximumStars]="field.properties['maxValue']"></sqx-stars>
</div>
<div *ngSwitchCase="'Dropdown'">
<select class="form-control" [formControl]="selectedFormControl">
<option [ngValue]="null"></option>
<option *ngFor="let value of field.properties['allowedValues']" [ngValue]="value">{{value}}</option>
</select>
</div>
<div *ngSwitchCase="'Radio'">
<div class="form-check form-check-inline" *ngFor="let value of field.properties['allowedValues']">
<input class="form-check-input" type="radio" [value]="value" [formControl]="selectedFormControl" />
<label class="form-check-label">
{{value}}
</label>
</div>
</div>
</div>
</div>
<div *ngSwitchCase="'String'">
<div [ngSwitch]="field.properties['editor']">
<div *ngSwitchCase="'Input'">
<input class="form-control" type="text" [formControl]="selectedFormControl" [placeholder]="field.displayPlaceholder" />
</div>
<div *ngSwitchCase="'Slug'">
<input class="form-control" type="text" [formControl]="selectedFormControl" [placeholder]="field.displayPlaceholder" sqxSlugifyInput />
</div>
<div *ngSwitchCase="'TextArea'">
<textarea class="form-control" [formControl]="selectedFormControl" rows="5" [placeholder]="field.displayPlaceholder"></textarea>
</div>
<div *ngSwitchCase="'RichText'">
<sqx-rich-editor [formControl]="selectedFormControl"></sqx-rich-editor>
</div>
<div *ngSwitchCase="'Markdown'">
<sqx-markdown-editor [formControl]="selectedFormControl"></sqx-markdown-editor>
</div>
<div *ngSwitchCase="'Dropdown'">
<select class="form-control" [formControl]="selectedFormControl">
<option [ngValue]="null"></option>
<option *ngFor="let value of field.properties['allowedValues']" [ngValue]="value">{{value}}</option>
</select>
</div>
<div *ngSwitchCase="'Radio'">
<div class="form-check form-check-inline" *ngFor="let value of field.properties['allowedValues']">
<input class="form-check-input" type="radio" value="{{value}}" [formControl]="selectedFormControl" />
<label class="form-check-label">
{{value}}
</label>
</div>
</div>
</div>
</div>
<div *ngSwitchCase="'Boolean'">
<div [ngSwitch]="field.properties['editor']">
<div *ngSwitchCase="'Toggle'">
<sqx-toggle [formControl]="selectedFormControl"></sqx-toggle>
</div>
<div *ngSwitchCase="'Checkbox'">
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" [formControl]="selectedFormControl" sqxIndeterminateValue />
</div>
</div>
</div>
</div>
<div *ngSwitchCase="'DateTime'">
<sqx-date-time-editor enforceTime="true" [mode]="field.properties['editor']" [formControl]="selectedFormControl"></sqx-date-time-editor>
</div>
<div *ngSwitchCase="'Geolocation'">
<sqx-geolocation-editor [formControl]="selectedFormControl"></sqx-geolocation-editor>
</div>
<div *ngSwitchCase="'Json'">
<sqx-json-editor [formControl]="selectedFormControl"></sqx-json-editor>
</div>
<div *ngSwitchCase="'Assets'">
<sqx-assets-editor [formControl]="selectedFormControl"></sqx-assets-editor>
</div>
<div *ngSwitchCase="'Tags'">
<sqx-tag-editor [formControl]="selectedFormControl"></sqx-tag-editor>
</div>
<div *ngSwitchCase="'References'">
<sqx-references-editor
[formControl]="selectedFormControl"
[language]="language"
[languages]="languages"
[schemaId]="field.properties['schemaId']">
</sqx-references-editor>
</div>
<div>
<ng-container *ngIf="field.properties.editorUrl">
<sqx-iframe-editor [url]="field.properties.editorUrl" [formControl]="selectedFormControl"></sqx-iframe-editor>
</ng-container>
<ng-container *ngIf="!field.properties.editorUrl">
<ng-container [ngSwitch]="field.properties.fieldType">
<ng-container *ngSwitchCase="'Number'">
<ng-container [ngSwitch]="field.properties['editor']">
<ng-container *ngSwitchCase="'Input'">
<input class="form-control" type="number" [formControl]="selectedFormControl" [placeholder]="field.displayPlaceholder" />
</ng-container>
<ng-container *ngSwitchCase="'Stars'">
<sqx-stars [formControl]="selectedFormControl" [maximumStars]="field.properties['maxValue']"></sqx-stars>
</ng-container>
<ng-container *ngSwitchCase="'Dropdown'">
<select class="form-control" [formControl]="selectedFormControl">
<option [ngValue]="null"></option>
<option *ngFor="let value of field.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 field.properties['allowedValues']">
<input class="form-check-input" type="radio" [value]="value" [formControl]="selectedFormControl" />
<label class="form-check-label">
{{value}}
</label>
</ng-container>
</ng-container>
</ng-container>
</ng-container>
<ng-container *ngSwitchCase="'String'">
<ng-container [ngSwitch]="field.properties['editor']">
<ng-container *ngSwitchCase="'Input'">
<input class="form-control" type="text" [formControl]="selectedFormControl" [placeholder]="field.displayPlaceholder" />
</ng-container>
<ng-container *ngSwitchCase="'Slug'">
<input class="form-control" type="text" [formControl]="selectedFormControl" [placeholder]="field.displayPlaceholder" sqxSlugifyInput />
</ng-container>
<ng-container *ngSwitchCase="'TextArea'">
<textarea class="form-control" [formControl]="selectedFormControl" rows="5" [placeholder]="field.displayPlaceholder"></textarea>
</ng-container>
<ng-container *ngSwitchCase="'RichText'">
<sqx-rich-editor [formControl]="selectedFormControl"></sqx-rich-editor>
</ng-container>
<ng-container *ngSwitchCase="'Markdown'">
<sqx-markdown-editor [formControl]="selectedFormControl"></sqx-markdown-editor>
</ng-container>
<ng-container *ngSwitchCase="'Dropdown'">
<select class="form-control" [formControl]="selectedFormControl">
<option [ngValue]="null"></option>
<option *ngFor="let value of field.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 field.properties['allowedValues']">
<input class="form-check-input" type="radio" value="{{value}}" [formControl]="selectedFormControl" />
<label class="form-check-label">
{{value}}
</label>
</ng-container>
</ng-container>
</ng-container>
</ng-container>
<ng-container *ngSwitchCase="'Boolean'">
<ng-container [ngSwitch]="field.properties['editor']">
<ng-container *ngSwitchCase="'Toggle'">
<sqx-toggle [formControl]="selectedFormControl"></sqx-toggle>
</ng-container>
<ng-container *ngSwitchCase="'Checkbox'">
<ng-container class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" [formControl]="selectedFormControl" sqxIndeterminateValue />
</ng-container>
</ng-container>
</ng-container>
</ng-container>
<ng-container *ngSwitchCase="'DateTime'">
<sqx-date-time-editor enforceTime="true" [mode]="field.properties['editor']" [formControl]="selectedFormControl"></sqx-date-time-editor>
</ng-container>
<ng-container *ngSwitchCase="'Geolocation'">
<sqx-geolocation-editor [formControl]="selectedFormControl"></sqx-geolocation-editor>
</ng-container>
<ng-container *ngSwitchCase="'Json'">
<sqx-json-editor [formControl]="selectedFormControl"></sqx-json-editor>
</ng-container>
<ng-container *ngSwitchCase="'Assets'">
<sqx-assets-editor [formControl]="selectedFormControl"></sqx-assets-editor>
</ng-container>
<ng-container *ngSwitchCase="'Tags'">
<sqx-tag-editor [formControl]="selectedFormControl"></sqx-tag-editor>
</ng-container>
<ng-container *ngSwitchCase="'References'">
<sqx-references-editor
[formControl]="selectedFormControl"
[language]="language"
[languages]="languages"
[schemaId]="field.properties['schemaId']">
</sqx-references-editor>
</ng-container>
</ng-container>
</ng-container>
</div>
<ng-container *ngIf="field.properties['hints']; let hints">

11
src/Squidex/app/features/schemas/pages/schema/types/boolean-ui.component.html

@ -1,4 +1,15 @@
<div [formGroup]="editForm">
<div class="form-group row">
<label class="col col-3 col-form-label" for="{{field.fieldId}}_editorUrl">Editor Url</label>
<div class="col col-6">
<input type="text" class="form-control" id="{{field.fieldId}}_editorUrl" formControlName="editorUrl" />
<small class="form-text text-muted">
Url to your plugin if you use a custom editor.
</small>
</div>
</div>
<div class="form-group row">
<label class="col col-3 col-form-label" for="{{field.fieldId}}_fieldPlaceholder">Placeholder</label>

5
src/Squidex/app/features/schemas/pages/schema/types/boolean-ui.component.ts

@ -31,11 +31,6 @@ export class BooleanUIComponent implements OnInit {
Validators.required
]));
this.editForm.setControl('placeholder',
new FormControl(this.properties.placeholder, [
Validators.maxLength(100)
]));
this.editForm.setControl('inlineEditable',
new FormControl(this.properties.inlineEditable));
}

11
src/Squidex/app/features/schemas/pages/schema/types/date-time-ui.component.html

@ -1,4 +1,15 @@
<div [formGroup]="editForm">
<div class="form-group row">
<label class="col col-3 col-form-label" for="{{field.fieldId}}_editorUrl">Editor Url</label>
<div class="col col-6">
<input type="text" class="form-control" id="{{field.fieldId}}_editorUrl" formControlName="editorUrl" />
<small class="form-text text-muted">
Url to your plugin if you use a custom editor.
</small>
</div>
</div>
<div class="form-group row">
<label class="col col-3 col-form-label" for="{{field.fieldId}}_fieldPlaceholder">Placeholder</label>

5
src/Squidex/app/features/schemas/pages/schema/types/date-time-ui.component.ts

@ -35,10 +35,5 @@ export class DateTimeUIComponent implements OnInit {
new FormControl(this.properties.editor, [
Validators.required
]));
this.editForm.setControl('placeholder',
new FormControl(this.properties.placeholder, [
Validators.maxLength(100)
]));
}
}

11
src/Squidex/app/features/schemas/pages/schema/types/geolocation-ui.component.html

@ -1,4 +1,15 @@
<div [formGroup]="editForm">
<div class="form-group row">
<label class="col col-3 col-form-label" for="{{field.fieldId}}_editorUrl">Editor Url</label>
<div class="col col-6">
<input type="text" class="form-control" id="{{field.fieldId}}_editorUrl" formControlName="editorUrl" />
<small class="form-text text-muted">
Url to your plugin if you use a custom editor.
</small>
</div>
</div>
<div class="form-group row">
<label class="col col-3 col-form-label">Editor</label>

11
src/Squidex/app/features/schemas/pages/schema/types/number-ui.component.html

@ -1,4 +1,15 @@
<div [formGroup]="editForm">
<div class="form-group row">
<label class="col col-3 col-form-label" for="{{field.fieldId}}_editorUrl">Editor Url</label>
<div class="col col-6">
<input type="text" class="form-control" id="{{field.fieldId}}_editorUrl" formControlName="editorUrl" />
<small class="form-text text-muted">
Url to your plugin if you use a custom editor.
</small>
</div>
</div>
<div class="form-group row">
<label class="col col-3 col-form-label" for="{{field.fieldId}}_fieldPlaceholder">Placeholder</label>

5
src/Squidex/app/features/schemas/pages/schema/types/number-ui.component.ts

@ -45,11 +45,6 @@ export class NumberUIComponent implements OnDestroy, OnInit {
Validators.required
]));
this.editForm.setControl('placeholder',
new FormControl(this.properties.placeholder, [
Validators.maxLength(100)
]));
this.editForm.setControl('allowedValues',
new FormControl(this.properties.allowedValues, []));

11
src/Squidex/app/features/schemas/pages/schema/types/string-ui.component.html

@ -1,4 +1,15 @@
<div [formGroup]="editForm">
<div class="form-group row">
<label class="col col-3 col-form-label" for="{{field.fieldId}}_editorUrl">Editor Url</label>
<div class="col col-6">
<input type="text" class="form-control" id="{{field.fieldId}}_editorUrl" formControlName="editorUrl" />
<small class="form-text text-muted">
Url to your plugin if you use a custom editor.
</small>
</div>
</div>
<div class="form-group row">
<label class="col col-3 col-form-label" for="{{field.fieldId}}_fieldPlaceholder">Placeholder</label>

5
src/Squidex/app/features/schemas/pages/schema/types/string-ui.component.ts

@ -43,11 +43,6 @@ export class StringUIComponent implements OnDestroy, OnInit {
Validators.required
]));
this.editForm.setControl('placeholder',
new FormControl(this.properties.placeholder, [
Validators.maxLength(100)
]));
this.editForm.setControl('allowedValues',
new FormControl(this.properties.allowedValues));

1
src/Squidex/app/framework/angular/forms/iframe-editor.component.html

@ -0,0 +1 @@
<iframe #iframe scrolling="no" [attr.src]="sanitizedUrl()" width="100%"></iframe>

8
src/Squidex/app/framework/angular/forms/iframe-editor.component.scss

@ -0,0 +1,8 @@
@import '_mixins';
@import '_vars';
iframe {
border: 0;
background: 0;
overflow: hidden;
}

111
src/Squidex/app/framework/angular/forms/iframe-editor.component.ts

@ -0,0 +1,111 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { AfterViewInit, ChangeDetectionStrategy, Component, ElementRef, forwardRef, Input, OnDestroy, OnInit, Renderer, ViewChild } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { DomSanitizer } from '@angular/platform-browser';
export const SQX_IFRAME_EDITOR_CONTROL_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => IFrameEditorComponent), multi: true
};
@Component({
selector: 'sqx-iframe-editor',
styleUrls: ['./iframe-editor.component.scss'],
templateUrl: './iframe-editor.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [SQX_IFRAME_EDITOR_CONTROL_VALUE_ACCESSOR]
})
export class IFrameEditorComponent implements ControlValueAccessor, AfterViewInit, OnInit, OnDestroy {
private windowMessageListener: Function;
private callChange = (v: any) => { /* NOOP */ };
private callTouched = () => { /* NOOP */ };
private value: string;
private isDisabled = false;
private isInitialized = false;
private plugin: HTMLIFrameElement;
@ViewChild('iframe')
public iframe: ElementRef;
@Input()
public url: string;
constructor(
private readonly sanitizer: DomSanitizer,
private readonly renderer: Renderer
) {
}
public ngOnDestroy() {
this.windowMessageListener();
}
public ngAfterViewInit() {
this.plugin = this.iframe.nativeElement;
}
public ngOnInit(): void {
this.windowMessageListener =
this.renderer.listenGlobal('window', 'message', (event: MessageEvent) => {
if (event.source === this.plugin.contentWindow) {
const { type } = event.data;
if (type === 'started') {
this.isInitialized = true;
if (this.plugin.contentWindow) {
this.plugin.contentWindow.postMessage({ type: 'disabled', disabled: this.isDisabled }, '*');
this.plugin.contentWindow.postMessage({ type: 'valueChanged', value: this.value }, '*');
}
} else if (type === 'resize') {
const { height } = event.data;
this.plugin.height = height + 'px';
} else if (type === 'valueChanged') {
const { value } = event.data;
if (this.value !== value) {
this.value = value;
this.callChange(value);
}
} else if (type === 'touched') {
this.callTouched();
}
}
});
}
public sanitizedUrl() {
return this.sanitizer.bypassSecurityTrustResourceUrl(this.url);
}
public writeValue(value: string) {
this.value = value;
if (this.isInitialized && this.plugin.contentWindow) {
this.plugin.contentWindow.postMessage({ type: 'valueChanged', value: this.value }, '*');
}
}
public setDisabledState(isDisabled: boolean): void {
this.isDisabled = isDisabled;
if (this.isInitialized && this.plugin.contentWindow) {
this.plugin.contentWindow.postMessage({ type: 'disabled', disabled: this.isDisabled }, '*');
}
}
public registerOnChange(fn: any) {
this.callChange = fn;
}
public registerOnTouched(fn: any) {
this.callTouched = fn;
}
}

1
src/Squidex/app/framework/declarations.ts

@ -14,6 +14,7 @@ export * from './angular/forms/dropdown.component';
export * from './angular/forms/file-drop.directive';
export * from './angular/forms/focus-on-init.directive';
export * from './angular/forms/form-error.component';
export * from './angular/forms/iframe-editor.component';
export * from './angular/forms/indeterminate-value.directive';
export * from './angular/forms/jscript-editor.component';
export * from './angular/forms/json-editor.component';

3
src/Squidex/app/framework/module.ts

@ -32,6 +32,7 @@ import {
FormErrorComponent,
FromNowPipe,
FullDateTimePipe,
IFrameEditorComponent,
IgnoreScrollbarDirective,
ImageSourceDirective,
IndeterminateValueDirective,
@ -101,6 +102,7 @@ import {
FormErrorComponent,
FromNowPipe,
FullDateTimePipe,
IFrameEditorComponent,
IgnoreScrollbarDirective,
ImageSourceDirective,
IndeterminateValueDirective,
@ -159,6 +161,7 @@ import {
FormsModule,
FromNowPipe,
FullDateTimePipe,
IFrameEditorComponent,
IgnoreScrollbarDirective,
ImageSourceDirective,
IndeterminateValueDirective,

48
src/Squidex/app/shared/services/schemas.fields.spec.ts

@ -43,9 +43,9 @@ describe('SchemaDetailsDto', () => {
});
it('should return configured fields as list fields if no schema field are declared', () => {
const field1 = createField(new AssetsFieldPropertiesDto(null, null, null, false, true), 1);
const field2 = createField(new AssetsFieldPropertiesDto(null, null, null, false, false), 2);
const field3 = createField(new AssetsFieldPropertiesDto(null, null, null, false, true), 3);
const field1 = createField(new AssetsFieldPropertiesDto(null, null, null, null, false, true), 1);
const field2 = createField(new AssetsFieldPropertiesDto(null, null, null, null, false, false), 2);
const field3 = createField(new AssetsFieldPropertiesDto(null, null, null, null, false, true), 3);
const schema = createSchema(new SchemaPropertiesDto(''), 1, [field1, field2, field3]);
@ -53,9 +53,9 @@ describe('SchemaDetailsDto', () => {
});
it('should return first fields as list fields if no schema field is declared', () => {
const field1 = createField(new AssetsFieldPropertiesDto(null, null, null, false, false), 1);
const field2 = createField(new AssetsFieldPropertiesDto(null, null, null, false, false), 2);
const field3 = createField(new AssetsFieldPropertiesDto(null, null, null, false, false), 3);
const field1 = createField(new AssetsFieldPropertiesDto(null, null, null, null, false, false), 1);
const field2 = createField(new AssetsFieldPropertiesDto(null, null, null, null, false, false), 2);
const field3 = createField(new AssetsFieldPropertiesDto(null, null, null, null, false, false), 3);
const schema = createSchema(new SchemaPropertiesDto(''), 1, [field1, field2, field3]);
@ -71,50 +71,50 @@ describe('SchemaDetailsDto', () => {
describe('FieldDto', () => {
it('should return label as display name', () => {
const field = createField(new AssetsFieldPropertiesDto('Label', null, null, true, false), 1);
const field = createField(new AssetsFieldPropertiesDto('Label', null, null, null, true, false), 1);
expect(field.displayName).toBe('Label');
});
it('should return name as display name if label is null', () => {
const field = createField(new AssetsFieldPropertiesDto(null, null, null, true, false), 1);
const field = createField(new AssetsFieldPropertiesDto(null, null, null, null, true, false), 1);
expect(field.displayName).toBe('field1');
});
it('should return name as display name label is empty', () => {
const field = createField(new AssetsFieldPropertiesDto('', null, null, true, false), 1);
const field = createField(new AssetsFieldPropertiesDto('', null, null, null, true, false), 1);
expect(field.displayName).toBe('field1');
});
it('should return placeholder as display placeholder', () => {
const field = createField(new AssetsFieldPropertiesDto(null, null, 'Placeholder', true, false), 1);
const field = createField(new AssetsFieldPropertiesDto(null, null, 'Placeholder', null, true, false), 1);
expect(field.displayPlaceholder).toBe('Placeholder');
});
it('should return empty as display placeholder if placeholder is null', () => {
const field = createField(new AssetsFieldPropertiesDto(null, null, null, true, false));
const field = createField(new AssetsFieldPropertiesDto(null, null, null, null, true, false));
expect(field.displayPlaceholder).toBe('');
});
it('should return localizable if partitioning is language', () => {
const field = createField(new AssetsFieldPropertiesDto(null, null, null, true, false), 1, 'language');
const field = createField(new AssetsFieldPropertiesDto(null, null, null, null, true, false), 1, 'language');
expect(field.isLocalizable).toBeTruthy();
});
it('should not return localizable if partitioning is invarient', () => {
const field = createField(new AssetsFieldPropertiesDto(null, null, null, true, false), 1, 'invariant');
const field = createField(new AssetsFieldPropertiesDto(null, null, null, null, true, false), 1, 'invariant');
expect(field.isLocalizable).toBeFalsy();
});
});
describe('AssetsField', () => {
const field = createField(new AssetsFieldPropertiesDto(null, null, null, true, false, 1, 1));
const field = createField(new AssetsFieldPropertiesDto(null, null, null, null, true, false, 1, 1));
it('should create validators', () => {
expect(field.createValidators(false).length).toBe(3);
@ -138,7 +138,7 @@ describe('AssetsField', () => {
});
describe('TagsField', () => {
const field = createField(new TagsFieldPropertiesDto(null, null, null, true, false, 1, 1));
const field = createField(new TagsFieldPropertiesDto(null, null, null, null, true, false, 1, 1));
it('should create validators', () => {
expect(field.createValidators(false).length).toBe(3);
@ -162,7 +162,7 @@ describe('TagsField', () => {
});
describe('BooleanField', () => {
const field = createField(new BooleanFieldPropertiesDto(null, null, null, true, false, false, 'Checkbox'));
const field = createField(new BooleanFieldPropertiesDto(null, null, null, null, true, false, false, 'Checkbox'));
it('should create validators', () => {
expect(field.createValidators(false).length).toBe(1);
@ -189,7 +189,7 @@ describe('BooleanField', () => {
describe('DateTimeField', () => {
const now = DateTime.parseISO_UTC('2017-10-12T16:30:10Z');
const field = createField(new DateTimeFieldPropertiesDto(null, null, null, true, false, 'Date'));
const field = createField(new DateTimeFieldPropertiesDto(null, null, null, null, true, false, 'Date'));
it('should create validators', () => {
expect(field.createValidators(false).length).toBe(1);
@ -204,13 +204,13 @@ describe('DateTimeField', () => {
});
it('should format to date', () => {
const dateField = createField(new DateTimeFieldPropertiesDto(null, null, null, true, false, 'Date'));
const dateField = createField(new DateTimeFieldPropertiesDto(null, null, null, null, true, false, 'Date'));
expect(dateField.formatValue('2017-12-12T16:00:00Z')).toBe('2017-12-12');
});
it('should format to date time', () => {
const dateTimeField = createField(new DateTimeFieldPropertiesDto(null, null, null, true, false, 'DateTime'));
const dateTimeField = createField(new DateTimeFieldPropertiesDto(null, null, null, null, true, false, 'DateTime'));
expect(dateTimeField.formatValue('2017-12-12T16:00:00Z')).toBe('2017-12-12 16:00:00');
});
@ -235,7 +235,7 @@ describe('DateTimeField', () => {
});
describe('GeolocationField', () => {
const field = createField(new GeolocationFieldPropertiesDto(null, null, null, true, false, 'Default'));
const field = createField(new GeolocationFieldPropertiesDto(null, null, null, null, true, false, 'Default'));
it('should create validators', () => {
expect(field.createValidators(false).length).toBe(1);
@ -255,7 +255,7 @@ describe('GeolocationField', () => {
});
describe('JsonField', () => {
const field = createField(new JsonFieldPropertiesDto(null, null, null, true, false));
const field = createField(new JsonFieldPropertiesDto(null, null, null, null, true, false));
it('should create validators', () => {
expect(field.createValidators(false).length).toBe(1);
@ -275,7 +275,7 @@ describe('JsonField', () => {
});
describe('NumberField', () => {
const field = createField(new NumberFieldPropertiesDto(null, null, null, true, false, false, 'Input', undefined, 3, 1, [1, 2, 3]));
const field = createField(new NumberFieldPropertiesDto(null, null, null, null, true, false, false, 'Input', undefined, 3, 1, [1, 2, 3]));
it('should create validators', () => {
expect(field.createValidators(false).length).toBe(4);
@ -297,7 +297,7 @@ describe('NumberField', () => {
});
describe('ReferencesField', () => {
const field = createField(new ReferencesFieldPropertiesDto(null, null, null, true, false, 1, 1));
const field = createField(new ReferencesFieldPropertiesDto(null, null, null, null, true, false, 1, 1));
it('should create validators', () => {
expect(field.createValidators(false).length).toBe(3);
@ -321,7 +321,7 @@ describe('ReferencesField', () => {
});
describe('StringField', () => {
const field = createField(new StringFieldPropertiesDto(null, null, null, true, false, false, 'Input', undefined, 'pattern', undefined, 3, 1, ['1', '2']));
const field = createField(new StringFieldPropertiesDto(null, null, null, null, true, false, false, 'Input', undefined, 'pattern', undefined, 3, 1, ['1', '2']));
it('should create validators', () => {
expect(field.createValidators(false).length).toBe(5);

55
src/Squidex/app/shared/services/schemas.service.ts

@ -61,31 +61,31 @@ export function createProperties(fieldType: string, values: Object | null = null
switch (fieldType) {
case 'Number':
properties = new NumberFieldPropertiesDto(null, null, null, false, false, false, 'Input');
properties = new NumberFieldPropertiesDto(null, null, null, null, false, false, false, 'Input');
break;
case 'String':
properties = new StringFieldPropertiesDto(null, null, null, false, false, false, 'Input');
properties = new StringFieldPropertiesDto(null, null, null, null, false, false, false, 'Input');
break;
case 'Boolean':
properties = new BooleanFieldPropertiesDto(null, null, null, false, false, false, 'Checkbox');
properties = new BooleanFieldPropertiesDto(null, null, null, null, false, false, false, 'Checkbox');
break;
case 'DateTime':
properties = new DateTimeFieldPropertiesDto(null, null, null, false, false, 'DateTime');
properties = new DateTimeFieldPropertiesDto(null, null, null, null, false, false, 'DateTime');
break;
case 'Geolocation':
properties = new GeolocationFieldPropertiesDto(null, null, null, false, false, 'Map');
properties = new GeolocationFieldPropertiesDto(null, null, null, null, false, false, 'Map');
break;
case 'Json':
properties = new JsonFieldPropertiesDto(null, null, null, false, false);
properties = new JsonFieldPropertiesDto(null, null, null, null, false, false);
break;
case 'References':
properties = new ReferencesFieldPropertiesDto(null, null, null, false, false);
properties = new ReferencesFieldPropertiesDto(null, null, null, null, false, false);
break;
case 'Assets':
properties = new AssetsFieldPropertiesDto(null, null, null, false, false);
properties = new AssetsFieldPropertiesDto(null, null, null, null, false, false);
break;
case 'Tags':
properties = new TagsFieldPropertiesDto(null, null, null, false, false);
properties = new TagsFieldPropertiesDto(null, null, null, null, false, false);
break;
default:
throw 'Invalid properties type';
@ -176,6 +176,7 @@ export abstract class FieldPropertiesDto {
public readonly label: string | null,
public readonly hints: string | null,
public readonly placeholder: string | null,
public readonly editorUrl: string | null,
public readonly isRequired: boolean,
public readonly isListField: boolean
) {
@ -191,7 +192,7 @@ export abstract class FieldPropertiesDto {
}
export class StringFieldPropertiesDto extends FieldPropertiesDto {
constructor(label: string | null, hints: string | null, placeholder: string | null,
constructor(label: string | null, hints: string | null, placeholder: string | null, editorUrl: string | null,
isRequired: boolean,
isListField: boolean,
public readonly inlineEditable: boolean,
@ -203,7 +204,7 @@ export class StringFieldPropertiesDto extends FieldPropertiesDto {
public readonly maxLength?: number,
public readonly allowedValues?: string[]
) {
super('String', label, hints, placeholder, isRequired, isListField);
super('String', label, hints, placeholder, editorUrl, isRequired, isListField);
}
public formatValue(value: any): string {
@ -252,7 +253,7 @@ export class StringFieldPropertiesDto extends FieldPropertiesDto {
}
export class NumberFieldPropertiesDto extends FieldPropertiesDto {
constructor(label: string | null, hints: string | null, placeholder: string | null,
constructor(label: string | null, hints: string | null, placeholder: string | null, editorUrl: string | null,
isRequired: boolean,
isListField: boolean,
public readonly inlineEditable: boolean,
@ -262,7 +263,7 @@ export class NumberFieldPropertiesDto extends FieldPropertiesDto {
public readonly minValue?: number,
public readonly allowedValues?: number[]
) {
super('Number', label, hints, placeholder, isRequired, isListField);
super('Number', label, hints, placeholder, editorUrl, isRequired, isListField);
}
public formatValue(value: any): string {
@ -307,7 +308,7 @@ export class NumberFieldPropertiesDto extends FieldPropertiesDto {
}
export class DateTimeFieldPropertiesDto extends FieldPropertiesDto {
constructor(label: string | null, hints: string | null, placeholder: string | null,
constructor(label: string | null, hints: string | null, placeholder: string | null, editorUrl: string | null,
isRequired: boolean,
isListField: boolean,
public readonly editor: string,
@ -316,7 +317,7 @@ export class DateTimeFieldPropertiesDto extends FieldPropertiesDto {
public readonly minValue?: string,
public readonly calculatedDefaultValue?: string
) {
super('DateTime', label, hints, placeholder, isRequired, isListField);
super('DateTime', label, hints, placeholder, editorUrl, isRequired, isListField);
}
public formatValue(value: any): string {
@ -361,14 +362,14 @@ export class DateTimeFieldPropertiesDto extends FieldPropertiesDto {
}
export class BooleanFieldPropertiesDto extends FieldPropertiesDto {
constructor(label: string | null, hints: string | null, placeholder: string | null,
constructor(label: string | null, hints: string | null, placeholder: string | null, editorUrl: string | null,
isRequired: boolean,
isListField: boolean,
public readonly inlineEditable: boolean,
public readonly editor: string,
public readonly defaultValue?: boolean
) {
super('Boolean', label, hints, placeholder, isRequired, isListField);
super('Boolean', label, hints, placeholder, editorUrl, isRequired, isListField);
}
public formatValue(value: any): string {
@ -395,12 +396,12 @@ export class BooleanFieldPropertiesDto extends FieldPropertiesDto {
}
export class GeolocationFieldPropertiesDto extends FieldPropertiesDto {
constructor(label: string | null, hints: string | null, placeholder: string | null,
constructor(label: string | null, hints: string | null, placeholder: string | null, editorUrl: string | null,
isRequired: boolean,
isListField: boolean,
public readonly editor: string
) {
super('Geolocation', label, hints, placeholder, isRequired, isListField);
super('Geolocation', label, hints, placeholder, editorUrl, isRequired, isListField);
}
public formatValue(value: any): string {
@ -423,14 +424,14 @@ export class GeolocationFieldPropertiesDto extends FieldPropertiesDto {
}
export class ReferencesFieldPropertiesDto extends FieldPropertiesDto {
constructor(label: string | null, hints: string | null, placeholder: string | null,
constructor(label: string | null, hints: string | null, placeholder: string | null, editorUrl: string | null,
isRequired: boolean,
isListField: boolean,
public readonly minItems?: number,
public readonly maxItems?: number,
public readonly schemaId?: string
) {
super('References', label, hints, placeholder, isRequired, isListField);
super('References', label, hints, placeholder, editorUrl, isRequired, isListField);
}
public formatValue(value: any): string {
@ -465,7 +466,7 @@ export class ReferencesFieldPropertiesDto extends FieldPropertiesDto {
}
export class AssetsFieldPropertiesDto extends FieldPropertiesDto {
constructor(label: string | null, hints: string | null, placeholder: string | null,
constructor(label: string | null, hints: string | null, placeholder: string | null, editorUrl: string | null,
isRequired: boolean,
isListField: boolean,
public readonly minItems?: number,
@ -481,7 +482,7 @@ export class AssetsFieldPropertiesDto extends FieldPropertiesDto {
public readonly aspectWidth?: number,
public readonly aspectHeight?: number
) {
super('Assets', label, hints, placeholder, isRequired, isListField);
super('Assets', label, hints, placeholder, editorUrl, isRequired, isListField);
}
public formatValue(value: any): string {
@ -516,13 +517,13 @@ export class AssetsFieldPropertiesDto extends FieldPropertiesDto {
}
export class TagsFieldPropertiesDto extends FieldPropertiesDto {
constructor(label: string | null, hints: string | null, placeholder: string | null,
constructor(label: string | null, hints: string | null, placeholder: string | null, editorUrl: string | null,
isRequired: boolean,
isListField: boolean,
public readonly minItems?: number,
public readonly maxItems?: number
) {
super('Tags', label, hints, placeholder, isRequired, isListField);
super('Tags', label, hints, placeholder, editorUrl, isRequired, isListField);
}
public formatValue(value: any): string {
@ -557,11 +558,11 @@ export class TagsFieldPropertiesDto extends FieldPropertiesDto {
}
export class JsonFieldPropertiesDto extends FieldPropertiesDto {
constructor(label: string | null, hints: string | null, placeholder: string | null,
constructor(label: string | null, hints: string | null, placeholder: string | null, editorUrl: string | null,
isRequired: boolean,
isListField: boolean
) {
super('Json', label, hints, placeholder, isRequired, isListField);
super('Json', label, hints, placeholder, editorUrl, isRequired, isListField);
}
public formatValue(value: any): string {

6
src/Squidex/app/shared/state/schemas.state.ts

@ -85,6 +85,12 @@ export class EditFieldForm extends Form<FormGroup> {
Validators.maxLength(1000)
]
],
placeholder: ['',
[
Validators.maxLength(1000)
]
],
editorUrl: null,
isRequired: false,
isListField: false
}));

36
tools/Migrate_01/Migrations/PopulateGrainIndexes.cs

@ -7,7 +7,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Orleans;
using Squidex.Domain.Apps.Entities.Apps;
@ -71,36 +70,32 @@ namespace Migrate_01.Migrations
return TaskHelper.Done;
});
var tasks =
appsByUser.Select(x =>
grainFactory.GetGrain<IAppsByUserIndex>(x.Key).RebuildAsync(x.Value))
.Union(new[]
{
grainFactory.GetGrain<IAppsByNameIndex>(SingleGrain.Id).RebuildAsync(appsByName)
});
await grainFactory.GetGrain<IAppsByNameIndex>(SingleGrain.Id).RebuildAsync(appsByName);
await Task.WhenAll(tasks);
foreach (var kvp in appsByUser)
{
await grainFactory.GetGrain<IAppsByUserIndex>(kvp.Key).RebuildAsync(kvp.Value);
}
}
private async Task RebuildRuleIndexes()
{
var schemasByApp = new Dictionary<Guid, HashSet<Guid>>();
var rulesByApp = new Dictionary<Guid, HashSet<Guid>>();
await statesForRules.ReadAllAsync((schema, version) =>
{
if (!schema.IsDeleted)
{
schemasByApp.GetOrAddNew(schema.AppId.Id).Add(schema.Id);
rulesByApp.GetOrAddNew(schema.AppId.Id).Add(schema.Id);
}
return TaskHelper.Done;
});
var tasks =
schemasByApp.Select(x =>
grainFactory.GetGrain<IRulesByAppIndex>(x.Key).RebuildAsync(x.Value));
await Task.WhenAll(tasks);
foreach (var kvp in rulesByApp)
{
await grainFactory.GetGrain<IRulesByAppIndex>(kvp.Key).RebuildAsync(kvp.Value);
}
}
private async Task RebuildSchemaIndexes()
@ -117,11 +112,10 @@ namespace Migrate_01.Migrations
return TaskHelper.Done;
});
var tasks =
schemasByApp.Select(x =>
grainFactory.GetGrain<ISchemasByAppIndex>(x.Key).RebuildAsync(x.Value));
await Task.WhenAll(tasks);
foreach (var kvp in schemasByApp)
{
await grainFactory.GetGrain<ISchemasByAppIndex>(kvp.Key).RebuildAsync(kvp.Value);
}
}
}
}

Loading…
Cancel
Save