Browse Source

TS improvements

pull/107/head
Sebastian Stehle 9 years ago
parent
commit
2df3ce73af
  1. 2
      src/Squidex/app/features/content/pages/content/content-page.component.ts
  2. 4
      src/Squidex/app/features/content/pages/contents/contents-page.component.ts
  3. 21
      src/Squidex/app/features/content/shared/assets-editor.component.ts
  4. 21
      src/Squidex/app/features/content/shared/references-editor.component.ts
  5. 2
      src/Squidex/app/features/dashboard/pages/dashboard-page.component.ts
  6. 24
      src/Squidex/app/features/schemas/pages/schema/schema-page.component.ts
  7. 3
      src/Squidex/app/features/schemas/pages/schemas/schema-form.component.ts
  8. 2
      src/Squidex/app/features/webhooks/pages/webhook-events-page.component.ts
  9. 2
      src/Squidex/app/features/webhooks/pages/webhook.component.ts
  10. 2
      src/Squidex/app/features/webhooks/pages/webhooks-page.component.ts
  11. 19
      src/Squidex/app/framework/angular/autocomplete.component.ts
  12. 12
      src/Squidex/app/framework/angular/confirm-click.directive.ts
  13. 3
      src/Squidex/app/framework/angular/copy.directive.ts
  14. 20
      src/Squidex/app/framework/angular/date-time-editor.component.ts
  15. 2
      src/Squidex/app/framework/angular/dialog-renderer.component.ts
  16. 13
      src/Squidex/app/framework/angular/dropdown.component.ts
  17. 31
      src/Squidex/app/framework/angular/geolocation-editor.component.ts
  18. 2
      src/Squidex/app/framework/angular/http-extensions-impl.ts
  19. 18
      src/Squidex/app/framework/angular/indeterminate-value.directive.ts
  20. 30
      src/Squidex/app/framework/angular/jscript-editor.component.ts
  21. 35
      src/Squidex/app/framework/angular/json-editor.component.ts
  22. 16
      src/Squidex/app/framework/angular/lowercase-input.directive.ts
  23. 24
      src/Squidex/app/framework/angular/markdown-editor.component.ts
  24. 4
      src/Squidex/app/framework/angular/modal-view.directive.ts
  25. 2
      src/Squidex/app/framework/angular/panel-container.directive.ts
  26. 24
      src/Squidex/app/framework/angular/rich-editor.component.ts
  27. 4
      src/Squidex/app/framework/angular/router-utils.ts
  28. 22
      src/Squidex/app/framework/angular/slider.component.ts
  29. 37
      src/Squidex/app/framework/angular/stars.component.ts
  30. 56
      src/Squidex/app/framework/angular/tag-editor.component.ts
  31. 21
      src/Squidex/app/framework/angular/toggle.component.ts
  32. 39
      src/Squidex/app/framework/angular/validators.ts
  33. 1
      src/Squidex/app/framework/declarations.ts
  34. 2
      src/Squidex/app/framework/services/local-cache.service.ts
  35. 92
      src/Squidex/app/framework/utils/types.spec.ts
  36. 70
      src/Squidex/app/framework/utils/types.ts
  37. 6
      src/Squidex/app/shared/components/asset.component.ts
  38. 4
      src/Squidex/app/shared/guards/resolve-app-languages.guard.ts
  39. 4
      src/Squidex/app/shared/guards/resolve-content.guard.ts
  40. 4
      src/Squidex/app/shared/guards/resolve-user.guard.ts
  41. 2
      src/Squidex/app/shared/interceptors/auth.interceptor.spec.ts
  42. 2
      src/Squidex/app/shared/interceptors/auth.interceptor.ts
  43. 6
      src/Squidex/app/shared/services/app-clients.service.ts
  44. 4
      src/Squidex/app/shared/services/app-contributors.service.ts
  45. 6
      src/Squidex/app/shared/services/app-languages.service.ts
  46. 19
      src/Squidex/app/shared/services/assets.service.spec.ts
  47. 23
      src/Squidex/app/shared/services/assets.service.ts
  48. 18
      src/Squidex/app/shared/services/auth.service.ts
  49. 16
      src/Squidex/app/shared/services/contents.service.spec.ts
  50. 14
      src/Squidex/app/shared/services/contents.service.ts
  51. 2
      src/Squidex/app/shared/services/plans.service.ts
  52. 32
      src/Squidex/app/shared/services/schemas.service.spec.ts
  53. 38
      src/Squidex/app/shared/services/schemas.service.ts
  54. 3
      src/Squidex/app/shared/services/ui.service.spec.ts
  55. 4
      src/Squidex/app/shared/services/webhooks.service.spec.ts
  56. 10
      src/Squidex/app/shared/services/webhooks.service.ts
  57. 6
      src/Squidex/app/shell/pages/internal/profile-menu.component.ts
  58. 2
      src/Squidex/tsconfig.json

2
src/Squidex/app/features/content/pages/content/content-page.component.ts

@ -139,7 +139,7 @@ export class ContentPageComponent extends AppComponentBase implements CanCompone
this.appNameOnce() this.appNameOnce()
.switchMap(app => this.contentsService.putContent(app, this.schema.name, this.contentId!, requestDto, this.version)) .switchMap(app => this.contentsService.putContent(app, this.schema.name, this.contentId!, requestDto, this.version))
.subscribe(dto => { .subscribe(dto => {
this.content = this.content.update(dto, this.authService.user.token); this.content = this.content.update(dto, this.authService.user!.token);
this.emitContentUpdated(this.content); this.emitContentUpdated(this.content);
this.notifyInfo('Content saved successfully.'); this.notifyInfo('Content saved successfully.');

4
src/Squidex/app/features/content/pages/contents/contents-page.component.ts

@ -123,7 +123,7 @@ export class ContentsPageComponent extends AppComponentBase implements OnDestroy
this.appNameOnce() this.appNameOnce()
.switchMap(app => this.contentsService.publishContent(app, this.schema.name, content.id, content.version)) .switchMap(app => this.contentsService.publishContent(app, this.schema.name, content.id, content.version))
.subscribe(() => { .subscribe(() => {
this.contentItems = this.contentItems.replaceBy('id', content.publish(this.authService.user.token)); this.contentItems = this.contentItems.replaceBy('id', content.publish(this.authService.user!.token));
}, error => { }, error => {
this.notifyError(error); this.notifyError(error);
}); });
@ -133,7 +133,7 @@ export class ContentsPageComponent extends AppComponentBase implements OnDestroy
this.appNameOnce() this.appNameOnce()
.switchMap(app => this.contentsService.unpublishContent(app, this.schema.name, content.id, content.version)) .switchMap(app => this.contentsService.unpublishContent(app, this.schema.name, content.id, content.version))
.subscribe(() => { .subscribe(() => {
this.contentItems = this.contentItems.replaceBy('id', content.unpublish(this.authService.user.token)); this.contentItems = this.contentItems.replaceBy('id', content.unpublish(this.authService.user!.token));
}, error => { }, error => {
this.notifyError(error); this.notifyError(error);
}); });

21
src/Squidex/app/features/content/shared/assets-editor.component.ts

@ -19,11 +19,10 @@ import {
AssetUpdated, AssetUpdated,
DialogService, DialogService,
ImmutableArray, ImmutableArray,
MessageBus MessageBus,
Types
} from 'shared'; } from 'shared';
const NOOP = () => { /* NOOP */ };
export const SQX_ASSETS_EDITOR_CONTROL_VALUE_ACCESSOR: any = { export const SQX_ASSETS_EDITOR_CONTROL_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => AssetsEditorComponent), multi: true provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => AssetsEditorComponent), multi: true
}; };
@ -36,8 +35,8 @@ export const SQX_ASSETS_EDITOR_CONTROL_VALUE_ACCESSOR: any = {
}) })
export class AssetsEditorComponent extends AppComponentBase implements ControlValueAccessor, OnDestroy, OnInit { export class AssetsEditorComponent extends AppComponentBase implements ControlValueAccessor, OnDestroy, OnInit {
private assetUpdatedSubscription: Subscription; private assetUpdatedSubscription: Subscription;
private changeCallback: (value: any) => void = NOOP; private onChange = (v: any) => { /* NOOP */ };
private touchedCallback: () => void = NOOP; private onTouched = () => { /* NOOP */ };
public newAssets = ImmutableArray.empty<File>(); public newAssets = ImmutableArray.empty<File>();
public oldAssets = ImmutableArray.empty<AssetDto>(); public oldAssets = ImmutableArray.empty<AssetDto>();
@ -65,10 +64,10 @@ export class AssetsEditorComponent extends AppComponentBase implements ControlVa
this.assetUpdatedSubscription.unsubscribe(); this.assetUpdatedSubscription.unsubscribe();
} }
public writeValue(value: any) { public writeValue(value: string[]) {
this.oldAssets = ImmutableArray.empty<AssetDto>(); this.oldAssets = ImmutableArray.empty<AssetDto>();
if (value && value.length > 0) { if (Types.isArrayOfString(value) && value.length > 0) {
const assetIds: string[] = value; const assetIds: string[] = value;
this.appNameOnce() this.appNameOnce()
@ -84,11 +83,11 @@ export class AssetsEditorComponent extends AppComponentBase implements ControlVa
} }
public registerOnChange(fn: any) { public registerOnChange(fn: any) {
this.changeCallback = fn; this.onChange = fn;
} }
public registerOnTouched(fn: any) { public registerOnTouched(fn: any) {
this.touchedCallback = fn; this.onTouched = fn;
} }
public addFiles(files: FileList) { public addFiles(files: FileList) {
@ -143,7 +142,7 @@ export class AssetsEditorComponent extends AppComponentBase implements ControlVa
ids = null; ids = null;
} }
this.touchedCallback(); this.onTouched();
this.changeCallback(ids); this.onChange(ids);
} }
} }

21
src/Squidex/app/features/content/shared/references-editor.component.ts

@ -19,11 +19,10 @@ import {
FieldDto, FieldDto,
ImmutableArray, ImmutableArray,
SchemaDetailsDto, SchemaDetailsDto,
SchemasService SchemasService,
Types
} from 'shared'; } from 'shared';
const NOOP = () => { /* NOOP */ };
export const SQX_REFERENCES_EDITOR_CONTROL_VALUE_ACCESSOR: any = { export const SQX_REFERENCES_EDITOR_CONTROL_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => ReferencesEditorComponent), multi: true provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => ReferencesEditorComponent), multi: true
}; };
@ -35,8 +34,8 @@ export const SQX_REFERENCES_EDITOR_CONTROL_VALUE_ACCESSOR: any = {
providers: [SQX_REFERENCES_EDITOR_CONTROL_VALUE_ACCESSOR] providers: [SQX_REFERENCES_EDITOR_CONTROL_VALUE_ACCESSOR]
}) })
export class ReferencesEditorComponent extends AppComponentBase implements ControlValueAccessor, OnInit { export class ReferencesEditorComponent extends AppComponentBase implements ControlValueAccessor, OnInit {
private changeCallback: (value: any) => void = NOOP; private onChange = (v: any) => { /* NOOP */ };
private touchedCallback: () => void = NOOP; private onTouched = () => { /* NOOP */ };
@Input() @Input()
public schemaId: string; public schemaId: string;
@ -73,10 +72,10 @@ export class ReferencesEditorComponent extends AppComponentBase implements Contr
}); });
} }
public writeValue(value: any) { public writeValue(value: string[]) {
this.contentItems = ImmutableArray.empty<ContentDto>(); this.contentItems = ImmutableArray.empty<ContentDto>();
if (value && value.length > 0) { if (Types.isArrayOfString(value) && value.length > 0) {
const contentIds: string[] = value; const contentIds: string[] = value;
this.appNameOnce() this.appNameOnce()
@ -92,11 +91,11 @@ export class ReferencesEditorComponent extends AppComponentBase implements Contr
} }
public registerOnChange(fn: any) { public registerOnChange(fn: any) {
this.changeCallback = fn; this.onChange = fn;
} }
public registerOnTouched(fn: any) { public registerOnTouched(fn: any) {
this.touchedCallback = fn; this.onTouched = fn;
} }
public canDrop() { public canDrop() {
@ -138,8 +137,8 @@ export class ReferencesEditorComponent extends AppComponentBase implements Contr
ids = null; ids = null;
} }
this.touchedCallback(); this.onTouched();
this.changeCallback(ids); this.onChange(ids);
} }
private loadFields() { private loadFields() {

2
src/Squidex/app/features/dashboard/pages/dashboard-page.component.ts

@ -146,7 +146,7 @@ export class DashboardPageComponent extends AppComponentBase implements OnInit {
}; };
}); });
this.profileDisplayName = this.authService.user.displayName; this.profileDisplayName = this.authService.user!.displayName;
} }
public showForum() { public showForum() {

24
src/Squidex/app/features/schemas/pages/schema/schema-page.component.ts

@ -110,7 +110,7 @@ export class SchemaPageComponent extends AppComponentBase implements OnInit {
this.appNameOnce() this.appNameOnce()
.switchMap(app => this.schemasService.publishSchema(app, this.schema.name, this.schema.version)).retry(2) .switchMap(app => this.schemasService.publishSchema(app, this.schema.name, this.schema.version)).retry(2)
.subscribe(() => { .subscribe(() => {
this.updateSchema(this.schema.publish(this.authService.user.token)); this.updateSchema(this.schema.publish(this.authService.user!.token));
}, error => { }, error => {
this.notifyError(error); this.notifyError(error);
}); });
@ -120,7 +120,7 @@ export class SchemaPageComponent extends AppComponentBase implements OnInit {
this.appNameOnce() this.appNameOnce()
.switchMap(app => this.schemasService.unpublishSchema(app, this.schema.name, this.schema.version)).retry(2) .switchMap(app => this.schemasService.unpublishSchema(app, this.schema.name, this.schema.version)).retry(2)
.subscribe(() => { .subscribe(() => {
this.updateSchema(this.schema.unpublish(this.authService.user.token)); this.updateSchema(this.schema.unpublish(this.authService.user!.token));
}, error => { }, error => {
this.notifyError(error); this.notifyError(error);
}); });
@ -130,7 +130,7 @@ export class SchemaPageComponent extends AppComponentBase implements OnInit {
this.appNameOnce() this.appNameOnce()
.switchMap(app => this.schemasService.enableField(app, this.schema.name, field.fieldId, this.schema.version)).retry(2) .switchMap(app => this.schemasService.enableField(app, this.schema.name, field.fieldId, this.schema.version)).retry(2)
.subscribe(() => { .subscribe(() => {
this.updateSchema(this.schema.updateField(field.enable(), this.authService.user.token)); this.updateSchema(this.schema.updateField(field.enable(), this.authService.user!.token));
}, error => { }, error => {
this.notifyError(error); this.notifyError(error);
}); });
@ -140,7 +140,7 @@ export class SchemaPageComponent extends AppComponentBase implements OnInit {
this.appNameOnce() this.appNameOnce()
.switchMap(app => this.schemasService.disableField(app, this.schema.name, field.fieldId, this.schema.version)).retry(2) .switchMap(app => this.schemasService.disableField(app, this.schema.name, field.fieldId, this.schema.version)).retry(2)
.subscribe(() => { .subscribe(() => {
this.updateSchema(this.schema.updateField(field.disable(), this.authService.user.token)); this.updateSchema(this.schema.updateField(field.disable(), this.authService.user!.token));
}, error => { }, error => {
this.notifyError(error); this.notifyError(error);
}); });
@ -150,7 +150,7 @@ export class SchemaPageComponent extends AppComponentBase implements OnInit {
this.appNameOnce() this.appNameOnce()
.switchMap(app => this.schemasService.lockField(app, this.schema.name, field.fieldId, this.schema.version)).retry(2) .switchMap(app => this.schemasService.lockField(app, this.schema.name, field.fieldId, this.schema.version)).retry(2)
.subscribe(() => { .subscribe(() => {
this.updateSchema(this.schema.updateField(field.lock(), this.authService.user.token)); this.updateSchema(this.schema.updateField(field.lock(), this.authService.user!.token));
}, error => { }, error => {
this.notifyError(error); this.notifyError(error);
}); });
@ -160,7 +160,7 @@ export class SchemaPageComponent extends AppComponentBase implements OnInit {
this.appNameOnce() this.appNameOnce()
.switchMap(app => this.schemasService.hideField(app, this.schema.name, field.fieldId, this.schema.version)).retry(2) .switchMap(app => this.schemasService.hideField(app, this.schema.name, field.fieldId, this.schema.version)).retry(2)
.subscribe(() => { .subscribe(() => {
this.updateSchema(this.schema.updateField(field.hide(), this.authService.user.token)); this.updateSchema(this.schema.updateField(field.hide(), this.authService.user!.token));
}, error => { }, error => {
this.notifyError(error); this.notifyError(error);
}); });
@ -170,7 +170,7 @@ export class SchemaPageComponent extends AppComponentBase implements OnInit {
this.appNameOnce() this.appNameOnce()
.switchMap(app => this.schemasService.deleteField(app, this.schema.name, field.fieldId, this.schema.version)).retry(2) .switchMap(app => this.schemasService.deleteField(app, this.schema.name, field.fieldId, this.schema.version)).retry(2)
.subscribe(() => { .subscribe(() => {
this.updateSchema(this.schema.removeField(field, this.authService.user.token)); this.updateSchema(this.schema.removeField(field, this.authService.user!.token));
}, error => { }, error => {
this.notifyError(error); this.notifyError(error);
}); });
@ -180,7 +180,7 @@ export class SchemaPageComponent extends AppComponentBase implements OnInit {
this.appNameOnce() this.appNameOnce()
.switchMap(app => this.schemasService.putFieldOrdering(app, this.schema.name, fields.map(t => t.fieldId), this.schema.version)).retry(2) .switchMap(app => this.schemasService.putFieldOrdering(app, this.schema.name, fields.map(t => t.fieldId), this.schema.version)).retry(2)
.subscribe(() => { .subscribe(() => {
this.updateSchema(this.schema.replaceFields(fields, this.authService.user.token)); this.updateSchema(this.schema.replaceFields(fields, this.authService.user!.token));
}, error => { }, error => {
this.notifyError(error); this.notifyError(error);
}); });
@ -192,7 +192,7 @@ export class SchemaPageComponent extends AppComponentBase implements OnInit {
this.appNameOnce() this.appNameOnce()
.switchMap(app => this.schemasService.putField(app, this.schema.name, field.fieldId, requestDto, this.schema.version)).retry(2) .switchMap(app => this.schemasService.putField(app, this.schema.name, field.fieldId, requestDto, this.schema.version)).retry(2)
.subscribe(() => { .subscribe(() => {
this.updateSchema(this.schema.updateField(field, this.authService.user.token)); this.updateSchema(this.schema.updateField(field, this.authService.user!.token));
}, error => { }, error => {
this.notifyError(error); this.notifyError(error);
}); });
@ -223,7 +223,7 @@ export class SchemaPageComponent extends AppComponentBase implements OnInit {
this.appNameOnce() this.appNameOnce()
.switchMap(app => this.schemasService.postField(app, this.schema.name, requestDto, this.schema.version)) .switchMap(app => this.schemasService.postField(app, this.schema.name, requestDto, this.schema.version))
.subscribe(dto => { .subscribe(dto => {
this.updateSchema(this.schema.addField(dto, this.authService.user.token)); this.updateSchema(this.schema.addField(dto, this.authService.user!.token));
this.resetFieldForm(); this.resetFieldForm();
}, error => { }, error => {
this.notifyError(error); this.notifyError(error);
@ -237,13 +237,13 @@ export class SchemaPageComponent extends AppComponentBase implements OnInit {
} }
public onSchemaSaved(properties: SchemaPropertiesDto) { public onSchemaSaved(properties: SchemaPropertiesDto) {
this.updateSchema(this.schema.update(properties, this.authService.user.token)); this.updateSchema(this.schema.update(properties, this.authService.user!.token));
this.editSchemaDialog.hide(); this.editSchemaDialog.hide();
} }
public onSchemaScriptsSaved(scripts: UpdateSchemaScriptsDto) { public onSchemaScriptsSaved(scripts: UpdateSchemaScriptsDto) {
this.updateSchema(this.schema.configureScripts(scripts, this.authService.user.token)); this.updateSchema(this.schema.configureScripts(scripts, this.authService.user!.token));
this.configureScriptsDialog.hide(); this.configureScriptsDialog.hide();
} }

3
src/Squidex/app/features/schemas/pages/schemas/schema-form.component.ts

@ -11,6 +11,7 @@ import { FormBuilder, Validators } from '@angular/forms';
import { import {
ApiUrlConfig, ApiUrlConfig,
AuthService, AuthService,
DateTime,
fadeAnimation, fadeAnimation,
SchemaDetailsDto, SchemaDetailsDto,
SchemasService, SchemasService,
@ -89,7 +90,7 @@ export class SchemaFormComponent {
const me = this.authService.user!.token; const me = this.authService.user!.token;
this.schemas.postSchema(this.appName, requestDto, me, undefined, schemaVersion) this.schemas.postSchema(this.appName, requestDto, me, DateTime.now(), schemaVersion)
.subscribe(dto => { .subscribe(dto => {
this.emitCreated(dto); this.emitCreated(dto);
this.resetCreateForm(); this.resetCreateForm();

2
src/Squidex/app/features/webhooks/pages/webhook-events-page.component.ts

@ -26,7 +26,7 @@ export class WebhookEventsPageComponent extends AppComponentBase implements OnIn
public eventsItems = ImmutableArray.empty<WebhookEventDto>(); public eventsItems = ImmutableArray.empty<WebhookEventDto>();
public eventsPager = new Pager(0); public eventsPager = new Pager(0);
public selectedEventId: string; public selectedEventId: string | null = null;
constructor(dialogs: DialogService, appsStore: AppsStoreService, constructor(dialogs: DialogService, appsStore: AppsStoreService,
private readonly webhooksService: WebhooksService private readonly webhooksService: WebhooksService

2
src/Squidex/app/features/webhooks/pages/webhook.component.ts

@ -91,7 +91,7 @@ export class WebhookComponent implements OnInit {
} else { } else {
return null; return null;
} }
}).filter(w => !!w)).sortByStringAsc(x => x.schema.name); }).filter(w => w !== null).map(w => w!)).sortByStringAsc(x => x.schema.name);
this.schemasToAdd = this.schemasToAdd =
ImmutableArray.of( ImmutableArray.of(

2
src/Squidex/app/features/webhooks/pages/webhooks-page.component.ts

@ -91,7 +91,7 @@ export class WebhooksPageComponent extends AppComponentBase implements OnInit {
this.appNameOnce() this.appNameOnce()
.switchMap(app => this.webhooksService.putWebhook(app, webhook.id, requestDto, webhook.version)) .switchMap(app => this.webhooksService.putWebhook(app, webhook.id, requestDto, webhook.version))
.subscribe(dto => { .subscribe(dto => {
this.webhooks = this.webhooks.replace(webhook, webhook.update(requestDto, this.authService.user.token)); this.webhooks = this.webhooks.replace(webhook, webhook.update(requestDto, this.authService.user!.token));
this.notifyInfo('Webhook saved.'); this.notifyInfo('Webhook saved.');
}, error => { }, error => {

19
src/Squidex/app/framework/angular/autocomplete.component.ts

@ -17,7 +17,6 @@ const KEY_ENTER = 13;
const KEY_ESCAPE = 27; const KEY_ESCAPE = 27;
const KEY_UP = 38; const KEY_UP = 38;
const KEY_DOWN = 40; const KEY_DOWN = 40;
const NOOP = () => { /* NOOP */ };
export const SQX_AUTOCOMPLETE_CONTROL_VALUE_ACCESSOR: any = { export const SQX_AUTOCOMPLETE_CONTROL_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => AutocompleteComponent), multi: true provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => AutocompleteComponent), multi: true
@ -31,8 +30,8 @@ export const SQX_AUTOCOMPLETE_CONTROL_VALUE_ACCESSOR: any = {
}) })
export class AutocompleteComponent implements ControlValueAccessor, OnDestroy, OnInit { export class AutocompleteComponent implements ControlValueAccessor, OnDestroy, OnInit {
private subscription: Subscription; private subscription: Subscription;
private changeCallback: (value: any) => void = NOOP; private onChange = (v: any) => { /* NOOP */ };
private touchedCallback: () => void = NOOP; private onTouched = () => { /* NOOP */ };
@Input() @Input()
public source: AutocompleteSource; public source: AutocompleteSource;
@ -57,7 +56,7 @@ export class AutocompleteComponent implements ControlValueAccessor, OnDestroy, O
public writeValue(value: any) { public writeValue(value: any) {
if (!value) { if (!value) {
this.resetValue(); this.resetForm();
} else { } else {
const item = this.items.find(i => i === value); const item = this.items.find(i => i === value);
@ -79,11 +78,11 @@ export class AutocompleteComponent implements ControlValueAccessor, OnDestroy, O
} }
public registerOnChange(fn: any) { public registerOnChange(fn: any) {
this.changeCallback = fn; this.onChange = fn;
} }
public registerOnTouched(fn: any) { public registerOnTouched(fn: any) {
this.touchedCallback = fn; this.onTouched = fn;
} }
public ngOnDestroy() { public ngOnDestroy() {
@ -119,7 +118,7 @@ export class AutocompleteComponent implements ControlValueAccessor, OnDestroy, O
this.down(); this.down();
return false; return false;
case KEY_ESCAPE: case KEY_ESCAPE:
this.resetValue(); this.resetForm();
this.reset(); this.reset();
return false; return false;
case KEY_ENTER: case KEY_ENTER:
@ -135,7 +134,7 @@ export class AutocompleteComponent implements ControlValueAccessor, OnDestroy, O
public blur() { public blur() {
this.reset(); this.reset();
this.touchedCallback(); this.onTouched();
} }
public selectItem(selection: any | null = null) { public selectItem(selection: any | null = null) {
@ -154,7 +153,7 @@ export class AutocompleteComponent implements ControlValueAccessor, OnDestroy, O
} else { } else {
this.queryInput.setValue(selection.toString(), { emitEvent: false }); this.queryInput.setValue(selection.toString(), { emitEvent: false });
} }
this.changeCallback(selection); this.onChange(selection);
} finally { } finally {
this.reset(); this.reset();
} }
@ -181,7 +180,7 @@ export class AutocompleteComponent implements ControlValueAccessor, OnDestroy, O
this.selectIndex(this.selectedIndex + 1); this.selectIndex(this.selectedIndex + 1);
} }
private resetValue() { private resetForm() {
this.queryInput.setValue(''); this.queryInput.setValue('');
} }

12
src/Squidex/app/framework/angular/confirm-click.directive.ts

@ -10,11 +10,13 @@ import { Directive, EventEmitter, HostListener, Input, OnDestroy, Output } from
import { DialogService } from './../services/dialog.service'; import { DialogService } from './../services/dialog.service';
class DelayEventEmitter<T> extends EventEmitter<T> { class DelayEventEmitter<T> extends EventEmitter<T> {
private delayedNexts: any[] = []; private delayedNexts: any[] | null = [];
public delayEmit() { public delayEmit() {
for (let callback of this.delayedNexts) { if (this.delayedNexts) {
callback(); for (let callback of this.delayedNexts) {
callback();
}
} }
} }
@ -23,7 +25,9 @@ class DelayEventEmitter<T> extends EventEmitter<T> {
} }
public subscribe(generatorOrNext?: any, error?: any, complete?: any): any { public subscribe(generatorOrNext?: any, error?: any, complete?: any): any {
this.delayedNexts.push(generatorOrNext); if (this.delayedNexts) {
this.delayedNexts.push(generatorOrNext);
}
return super.subscribe(generatorOrNext, error, complete); return super.subscribe(generatorOrNext, error, complete);
} }

3
src/Squidex/app/framework/angular/copy.directive.ts

@ -7,6 +7,7 @@
import { Directive, HostListener, Input } from '@angular/core'; import { Directive, HostListener, Input } from '@angular/core';
import { Types } from './../utils/types';
import { DialogService, Notification } from './../services/dialog.service'; import { DialogService, Notification } from './../services/dialog.service';
@Directive({ @Directive({
@ -48,7 +49,7 @@ export class CopyDirective {
console.log('Copy failed'); console.log('Copy failed');
} }
if (currentFocus && typeof currentFocus.focus === 'function') { if (currentFocus && Types.isFunction(currentFocus.focus)) {
currentFocus.focus(); currentFocus.focus();
} }

20
src/Squidex/app/framework/angular/date-time-editor.component.ts

@ -12,8 +12,6 @@ import * as moment from 'moment';
let Pikaday = require('pikaday/pikaday'); let Pikaday = require('pikaday/pikaday');
const NOOP = () => { /* NOOP */ };
export const SQX_DATE_TIME_EDITOR_CONTROL_VALUE_ACCESSOR: any = { export const SQX_DATE_TIME_EDITOR_CONTROL_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => DateTimeEditorComponent), multi: true provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => DateTimeEditorComponent), multi: true
}; };
@ -31,8 +29,8 @@ export class DateTimeEditorComponent implements ControlValueAccessor, OnDestroy,
private timeValue: any | null = null; private timeValue: any | null = null;
private dateValue: any | null = null; private dateValue: any | null = null;
private suppressEvents = false; private suppressEvents = false;
private changeCallback: (value: any) => void = NOOP; private onChange = (v: any) => { /* NOOP */ };
private touchedCallback: () => void = NOOP; private onTouched = () => { /* NOOP */ };
@Input() @Input()
public mode: string; public mode: string;
@ -116,11 +114,11 @@ export class DateTimeEditorComponent implements ControlValueAccessor, OnDestroy,
} }
public registerOnChange(fn: any) { public registerOnChange(fn: any) {
this.changeCallback = fn; this.onChange = fn;
} }
public registerOnTouched(fn: any) { public registerOnTouched(fn: any) {
this.touchedCallback = fn; this.onTouched = fn;
} }
public ngAfterViewInit() { public ngAfterViewInit() {
@ -140,7 +138,7 @@ export class DateTimeEditorComponent implements ControlValueAccessor, OnDestroy,
} }
public touched() { public touched() {
this.touchedCallback(); this.onTouched();
} }
public writeNow() { public writeNow() {
@ -159,14 +157,14 @@ export class DateTimeEditorComponent implements ControlValueAccessor, OnDestroy,
this.dateValue = null; this.dateValue = null;
this.changeCallback(null); this.onChange(null);
this.touchedCallback(); this.onTouched();
return false; return false;
} }
private updateValue() { private updateValue() {
let result: string | null; let result: string | null = null;
if ((this.dateValue && !this.dateValue.isValid()) || (this.timeValue && !this.timeValue.isValid())) { if ((this.dateValue && !this.dateValue.isValid()) || (this.timeValue && !this.timeValue.isValid())) {
result = 'Invalid DateTime'; result = 'Invalid DateTime';
@ -184,7 +182,7 @@ export class DateTimeEditorComponent implements ControlValueAccessor, OnDestroy,
} }
} }
this.changeCallback(result); this.onChange(result);
} }
private updateControls() { private updateControls() {

2
src/Squidex/app/framework/angular/dialog-renderer.component.ts

@ -32,7 +32,7 @@ export class DialogRendererComponent implements OnDestroy, OnInit {
private notificationsSubscription: Subscription; private notificationsSubscription: Subscription;
public dialogView = new ModalView(false, true); public dialogView = new ModalView(false, true);
public dialogRequest: DialogRequest; public dialogRequest: DialogRequest | null = null;
public notifications: Notification[] = []; public notifications: Notification[] = [];

13
src/Squidex/app/framework/angular/dropdown.component.ts

@ -12,7 +12,6 @@ const KEY_ENTER = 13;
const KEY_ESCAPE = 27; const KEY_ESCAPE = 27;
const KEY_UP = 38; const KEY_UP = 38;
const KEY_DOWN = 40; const KEY_DOWN = 40;
const NOOP = () => { /* NOOP */ };
import { ModalView } from './../utils/modal-view'; import { ModalView } from './../utils/modal-view';
@ -27,8 +26,8 @@ export const SQX_DROPDOWN_CONTROL_VALUE_ACCESSOR: any = {
providers: [SQX_DROPDOWN_CONTROL_VALUE_ACCESSOR] providers: [SQX_DROPDOWN_CONTROL_VALUE_ACCESSOR]
}) })
export class DropdownComponent implements AfterContentInit, ControlValueAccessor { export class DropdownComponent implements AfterContentInit, ControlValueAccessor {
private changeCallback: (value: any) => void = NOOP; private onChange = (v: any) => { /* NOOP */ };
private touchedCallback: () => void = NOOP; private onTouched = () => { /* NOOP */ };
@Input() @Input()
public items: any[] = []; public items: any[] = [];
@ -69,11 +68,11 @@ export class DropdownComponent implements AfterContentInit, ControlValueAccessor
} }
public registerOnChange(fn: any) { public registerOnChange(fn: any) {
this.changeCallback = fn; this.onChange = fn;
} }
public registerOnTouched(fn: any) { public registerOnTouched(fn: any) {
this.touchedCallback = fn; this.onTouched = fn;
} }
public onKeyDown(event: KeyboardEvent) { public onKeyDown(event: KeyboardEvent) {
@ -95,7 +94,7 @@ export class DropdownComponent implements AfterContentInit, ControlValueAccessor
public open() { public open() {
this.dropdown.show(); this.dropdown.show();
this.touchedCallback(); this.onTouched();
} }
public selectIndexAndClose(selectedIndex: number) { public selectIndexAndClose(selectedIndex: number) {
@ -132,7 +131,7 @@ export class DropdownComponent implements AfterContentInit, ControlValueAccessor
this.selectedIndex = selectedIndex; this.selectedIndex = selectedIndex;
this.selectedItem = value; this.selectedItem = value;
this.changeCallback(value); this.onChange(value);
} }
} }
} }

31
src/Squidex/app/framework/angular/geolocation-editor.component.ts

@ -8,17 +8,22 @@
import { AfterViewInit, Component, ElementRef, forwardRef, ViewChild } from '@angular/core'; import { AfterViewInit, Component, ElementRef, forwardRef, ViewChild } from '@angular/core';
import { ControlValueAccessor, FormBuilder, NG_VALUE_ACCESSOR } from '@angular/forms'; import { ControlValueAccessor, FormBuilder, NG_VALUE_ACCESSOR } from '@angular/forms';
import { Types } from './../utils/types';
import { ResourceLoaderService } from './../services/resource-loader.service'; import { ResourceLoaderService } from './../services/resource-loader.service';
import { ValidatorsEx } from './validators'; import { ValidatorsEx } from './validators';
const NOOP = () => { /* NOOP */ };
declare var L: any; declare var L: any;
export const SQX_GEOLOCATION_EDITOR_CONTROL_VALUE_ACCESSOR: any = { export const SQX_GEOLOCATION_EDITOR_CONTROL_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => GeolocationEditorComponent), multi: true provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => GeolocationEditorComponent), multi: true
}; };
interface Geolocation {
latitude: number;
longitude: number;
}
@Component({ @Component({
selector: 'sqx-geolocation-editor', selector: 'sqx-geolocation-editor',
styleUrls: ['./geolocation-editor.component.scss'], styleUrls: ['./geolocation-editor.component.scss'],
@ -26,11 +31,11 @@ export const SQX_GEOLOCATION_EDITOR_CONTROL_VALUE_ACCESSOR: any = {
providers: [SQX_GEOLOCATION_EDITOR_CONTROL_VALUE_ACCESSOR] providers: [SQX_GEOLOCATION_EDITOR_CONTROL_VALUE_ACCESSOR]
}) })
export class GeolocationEditorComponent implements ControlValueAccessor, AfterViewInit { export class GeolocationEditorComponent implements ControlValueAccessor, AfterViewInit {
private changeCallback: (value: any) => void = NOOP; private onChange = (v: any) => { /* NOOP */ };
private touchedCallback: () => void = NOOP; private onTouched = () => { /* NOOP */ };
private marker: any; private marker: any;
private map: any; private map: any;
private value: any; private value: Geolocation | null = null;
public get hasValue() { public get hasValue() {
return !!this.value; return !!this.value;
@ -59,8 +64,12 @@ export class GeolocationEditorComponent implements ControlValueAccessor, AfterVi
) { ) {
} }
public writeValue(value: any) { public writeValue(value: Geolocation) {
this.value = value; if (Types.isObject(value) && Types.isNumber(value.latitude) && Types.isNumber(value.longitude)) {
this.value = value;
} else {
this.value = null;
}
if (this.marker) { if (this.marker) {
this.updateMarker(true, false); this.updateMarker(true, false);
@ -102,11 +111,11 @@ export class GeolocationEditorComponent implements ControlValueAccessor, AfterVi
} }
public registerOnChange(fn: any) { public registerOnChange(fn: any) {
this.changeCallback = fn; this.onChange = fn;
} }
public registerOnTouched(fn: any) { public registerOnTouched(fn: any) {
this.touchedCallback = fn; this.onTouched = fn;
} }
public updateValueByInput() { public updateValueByInput() {
@ -201,8 +210,8 @@ export class GeolocationEditorComponent implements ControlValueAccessor, AfterVi
} }
if (fireEvent) { if (fireEvent) {
this.changeCallback(this.value); this.onChange(this.value);
this.touchedCallback(); this.onTouched();
} }
} }
} }

2
src/Squidex/app/framework/angular/http-extensions-impl.ts

@ -78,7 +78,7 @@ export module HTTP {
} }
} }
function handleVersion(httpRequest: Observable<HttpResponse<any>>, version: Version): Observable<any> { function handleVersion(httpRequest: Observable<HttpResponse<any>>, version?: Version): Observable<any> {
return httpRequest.do((response: HttpResponse<any>) => { return httpRequest.do((response: HttpResponse<any>) => {
if (version && response.status.toString().indexOf('2') === 0 && response.headers) { if (version && response.status.toString().indexOf('2') === 0 && response.headers) {
const etag = response.headers.get('etag'); const etag = response.headers.get('etag');

18
src/Squidex/app/framework/angular/indeterminate-value.directive.ts

@ -8,7 +8,7 @@
import { Directive, forwardRef, ElementRef, HostListener, Renderer } from '@angular/core'; import { Directive, forwardRef, ElementRef, HostListener, Renderer } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
const NOOP = () => { /* NOOP */ }; import { Types } from './../utils/types';
export const SQX_INDETERMINATE_VALUE_CONTROL_VALUE_ACCESSOR: any = { export const SQX_INDETERMINATE_VALUE_CONTROL_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => IndeterminateValueDirective), multi: true provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => IndeterminateValueDirective), multi: true
@ -19,8 +19,8 @@ export const SQX_INDETERMINATE_VALUE_CONTROL_VALUE_ACCESSOR: any = {
providers: [SQX_INDETERMINATE_VALUE_CONTROL_VALUE_ACCESSOR] providers: [SQX_INDETERMINATE_VALUE_CONTROL_VALUE_ACCESSOR]
}) })
export class IndeterminateValueDirective implements ControlValueAccessor { export class IndeterminateValueDirective implements ControlValueAccessor {
private changeCallback: (value: any) => void = NOOP; private onChange = (v: any) => { /* NOOP */ };
private touchedCallback: () => void = NOOP; private onTouched = () => { /* NOOP */ };
constructor( constructor(
private readonly renderer: Renderer, private readonly renderer: Renderer,
@ -30,16 +30,16 @@ export class IndeterminateValueDirective implements ControlValueAccessor {
@HostListener('change', ['$event.target.value']) @HostListener('change', ['$event.target.value'])
public onChange(value: any) { public onChange(value: any) {
this.changeCallback(value); this.onChange(value);
} }
@HostListener('blur') @HostListener('blur')
public onTouched() { public onTouched() {
this.touchedCallback(); this.onTouched();
} }
public writeValue(value: any) { public writeValue(value: boolean | number | undefined) {
if (value === undefined || value === null) { if (!Types.isBoolean(value)) {
this.renderer.setElementProperty(this.element.nativeElement, 'indeterminate', true); this.renderer.setElementProperty(this.element.nativeElement, 'indeterminate', true);
} else { } else {
this.renderer.setElementProperty(this.element.nativeElement, 'checked', value); this.renderer.setElementProperty(this.element.nativeElement, 'checked', value);
@ -51,10 +51,10 @@ export class IndeterminateValueDirective implements ControlValueAccessor {
} }
public registerOnChange(fn: any) { public registerOnChange(fn: any) {
this.changeCallback = fn; this.onChange = fn;
} }
public registerOnTouched(fn: any) { public registerOnTouched(fn: any) {
this.touchedCallback = fn; this.onTouched = fn;
} }
} }

30
src/Squidex/app/framework/angular/jscript-editor.component.ts

@ -9,12 +9,12 @@ import { AfterViewInit, Component, forwardRef, ElementRef, ViewChild } from '@an
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import { Types } from './../utils/types';
import { ResourceLoaderService } from './../services/resource-loader.service'; import { ResourceLoaderService } from './../services/resource-loader.service';
declare var ace: any; declare var ace: any;
const NOOP = () => { /* NOOP */ };
export const SQX_JSCRIPT_EDITOR_CONTROL_VALUE_ACCESSOR: any = { export const SQX_JSCRIPT_EDITOR_CONTROL_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => JscriptEditorComponent), multi: true provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => JscriptEditorComponent), multi: true
}; };
@ -26,11 +26,11 @@ export const SQX_JSCRIPT_EDITOR_CONTROL_VALUE_ACCESSOR: any = {
providers: [SQX_JSCRIPT_EDITOR_CONTROL_VALUE_ACCESSOR] providers: [SQX_JSCRIPT_EDITOR_CONTROL_VALUE_ACCESSOR]
}) })
export class JscriptEditorComponent implements ControlValueAccessor, AfterViewInit { export class JscriptEditorComponent implements ControlValueAccessor, AfterViewInit {
private changeCallback: (value: any) => void = NOOP; private onChange = (v: any) => { /* NOOP */ };
private touchedCallback: () => void = NOOP; private onTouched = () => { /* NOOP */ };
private valueChanged = new Subject(); private valueChanged = new Subject();
private aceEditor: any; private aceEditor: any;
private oldValue: string; private value: string;
private isDisabled = false; private isDisabled = false;
@ViewChild('editor') @ViewChild('editor')
@ -41,11 +41,11 @@ export class JscriptEditorComponent implements ControlValueAccessor, AfterViewIn
) { ) {
} }
public writeValue(value: any) { public writeValue(value: string) {
this.oldValue = value; this.value = Types.isString(value) ? value : '';
if (this.aceEditor) { if (this.aceEditor) {
this.setValue(value); this.setValue(this.value);
} }
} }
@ -58,11 +58,11 @@ export class JscriptEditorComponent implements ControlValueAccessor, AfterViewIn
} }
public registerOnChange(fn: any) { public registerOnChange(fn: any) {
this.changeCallback = fn; this.onChange = fn;
} }
public registerOnTouched(fn: any) { public registerOnTouched(fn: any) {
this.touchedCallback = fn; this.onTouched = fn;
} }
public ngAfterViewInit() { public ngAfterViewInit() {
@ -78,11 +78,11 @@ export class JscriptEditorComponent implements ControlValueAccessor, AfterViewIn
this.aceEditor.setReadOnly(this.isDisabled); this.aceEditor.setReadOnly(this.isDisabled);
this.aceEditor.setFontSize(14); this.aceEditor.setFontSize(14);
this.setValue(this.oldValue); this.setValue(this.value);
this.aceEditor.on('blur', () => { this.aceEditor.on('blur', () => {
this.changeValue(); this.changeValue();
this.touchedCallback(); this.onTouched();
}); });
this.aceEditor.on('change', () => { this.aceEditor.on('change', () => {
@ -94,11 +94,11 @@ export class JscriptEditorComponent implements ControlValueAccessor, AfterViewIn
private changeValue() { private changeValue() {
const newValue = this.aceEditor.getValue(); const newValue = this.aceEditor.getValue();
if (this.oldValue !== newValue) { if (this.value !== newValue) {
this.changeCallback(newValue); this.onChange(newValue);
} }
this.oldValue = newValue; this.value = newValue;
} }
private setValue(value: string) { private setValue(value: string) {

35
src/Squidex/app/framework/angular/json-editor.component.ts

@ -13,8 +13,6 @@ import { ResourceLoaderService } from './../services/resource-loader.service';
declare var ace: any; declare var ace: any;
const NOOP = () => { /* NOOP */ };
export const SQX_JSON_EDITOR_CONTROL_VALUE_ACCESSOR: any = { export const SQX_JSON_EDITOR_CONTROL_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => JsonEditorComponent), multi: true provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => JsonEditorComponent), multi: true
}; };
@ -26,12 +24,12 @@ export const SQX_JSON_EDITOR_CONTROL_VALUE_ACCESSOR: any = {
providers: [SQX_JSON_EDITOR_CONTROL_VALUE_ACCESSOR] providers: [SQX_JSON_EDITOR_CONTROL_VALUE_ACCESSOR]
}) })
export class JsonEditorComponent implements ControlValueAccessor, AfterViewInit { export class JsonEditorComponent implements ControlValueAccessor, AfterViewInit {
private changeCallback: (value: any) => void = NOOP; private onChange = (v: any) => { /* NOOP */ };
private touchedCallback: () => void = NOOP; private onTouched = () => { /* NOOP */ };
private valueChanged = new Subject(); private valueChanged = new Subject();
private aceEditor: any; private aceEditor: any;
private oldValue: any; private value: any;
private oldValueString: string; private valueString: string;
private isDisabled = false; private isDisabled = false;
@ViewChild('editor') @ViewChild('editor')
@ -43,8 +41,13 @@ export class JsonEditorComponent implements ControlValueAccessor, AfterViewInit
} }
public writeValue(value: any) { public writeValue(value: any) {
this.oldValue = value; this.value = value;
this.oldValueString = JSON.stringify(value);
try {
this.valueString = JSON.stringify(value);
} catch (e) {
this.valueString = '';
}
if (this.aceEditor) { if (this.aceEditor) {
this.setValue(value); this.setValue(value);
@ -60,11 +63,11 @@ export class JsonEditorComponent implements ControlValueAccessor, AfterViewInit
} }
public registerOnChange(fn: any) { public registerOnChange(fn: any) {
this.changeCallback = fn; this.onChange = fn;
} }
public registerOnTouched(fn: any) { public registerOnTouched(fn: any) {
this.touchedCallback = fn; this.onTouched = fn;
} }
public ngAfterViewInit() { public ngAfterViewInit() {
@ -80,11 +83,11 @@ export class JsonEditorComponent implements ControlValueAccessor, AfterViewInit
this.aceEditor.setReadOnly(this.isDisabled); this.aceEditor.setReadOnly(this.isDisabled);
this.aceEditor.setFontSize(14); this.aceEditor.setFontSize(14);
this.setValue(this.oldValue); this.setValue(this.value);
this.aceEditor.on('blur', () => { this.aceEditor.on('blur', () => {
this.changeValue(); this.changeValue();
this.touchedCallback(); this.onTouched();
}); });
this.aceEditor.on('change', () => { this.aceEditor.on('change', () => {
@ -108,12 +111,12 @@ export class JsonEditorComponent implements ControlValueAccessor, AfterViewInit
const newValueString = JSON.stringify(newValue); const newValueString = JSON.stringify(newValue);
if (this.oldValueString !== newValueString) { if (this.valueString !== newValueString) {
this.changeCallback(newValue); this.onChange(newValue);
} }
this.oldValue = newValue; this.value = newValue;
this.oldValueString = newValueString; this.valueString = newValueString;
} }
private setValue(value: any) { private setValue(value: any) {

16
src/Squidex/app/framework/angular/lowercase-input.directive.ts

@ -8,8 +8,6 @@
import { Directive, forwardRef, ElementRef, HostListener, Renderer } from '@angular/core'; import { Directive, forwardRef, ElementRef, HostListener, Renderer } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
const NOOP = () => { /* NOOP */ };
export const SQX_LOWERCASE_INPUT_VALUE_ACCESSOR: any = { export const SQX_LOWERCASE_INPUT_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => LowerCaseInputDirective), multi: true provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => LowerCaseInputDirective), multi: true
}; };
@ -19,8 +17,8 @@ export const SQX_LOWERCASE_INPUT_VALUE_ACCESSOR: any = {
providers: [SQX_LOWERCASE_INPUT_VALUE_ACCESSOR] providers: [SQX_LOWERCASE_INPUT_VALUE_ACCESSOR]
}) })
export class LowerCaseInputDirective implements ControlValueAccessor { export class LowerCaseInputDirective implements ControlValueAccessor {
private changeCallback: (value: any) => void = NOOP; private onChange = (v: any) => { /* NOOP */ };
private touchedCallback: () => void = NOOP; private onTouched = () => { /* NOOP */ };
constructor( constructor(
private readonly element: ElementRef, private readonly element: ElementRef,
@ -33,16 +31,16 @@ export class LowerCaseInputDirective implements ControlValueAccessor {
const normalizedValue = (value == null ? '' : value.toString()).toLowerCase(); const normalizedValue = (value == null ? '' : value.toString()).toLowerCase();
this.renderer.setElementProperty(this.element.nativeElement, 'value', normalizedValue); this.renderer.setElementProperty(this.element.nativeElement, 'value', normalizedValue);
this.changeCallback(normalizedValue); this.onChange(normalizedValue);
} }
@HostListener('blur') @HostListener('blur')
public onTouched() { public onTouched() {
this.touchedCallback(); this.onTouched();
} }
public writeValue(value: any) { public writeValue(value: any) {
const normalizedValue = (value == null ? '' : value.toString()).toLowerCase(); const normalizedValue = value ? '' : value.toString().toLowerCase();
this.renderer.setElementProperty(this.element.nativeElement, 'value', normalizedValue); this.renderer.setElementProperty(this.element.nativeElement, 'value', normalizedValue);
} }
@ -52,10 +50,10 @@ export class LowerCaseInputDirective implements ControlValueAccessor {
} }
public registerOnChange(fn: any) { public registerOnChange(fn: any) {
this.changeCallback = fn; this.onChange = fn;
} }
public registerOnTouched(fn: any) { public registerOnTouched(fn: any) {
this.touchedCallback = fn; this.onTouched = fn;
} }
} }

24
src/Squidex/app/framework/angular/markdown-editor.component.ts

@ -8,12 +8,12 @@
import { AfterViewInit, Component, forwardRef, ElementRef, ViewChild } from '@angular/core'; import { AfterViewInit, Component, forwardRef, ElementRef, ViewChild } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { Types } from './../utils/types';
import { ResourceLoaderService } from './../services/resource-loader.service'; import { ResourceLoaderService } from './../services/resource-loader.service';
declare var SimpleMDE: any; declare var SimpleMDE: any;
const NOOP = () => { /* NOOP */ };
export const SQX_MARKDOWN_EDITOR_CONTROL_VALUE_ACCESSOR: any = { export const SQX_MARKDOWN_EDITOR_CONTROL_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => MarkdownEditorComponent), multi: true provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => MarkdownEditorComponent), multi: true
}; };
@ -25,10 +25,10 @@ export const SQX_MARKDOWN_EDITOR_CONTROL_VALUE_ACCESSOR: any = {
providers: [SQX_MARKDOWN_EDITOR_CONTROL_VALUE_ACCESSOR] providers: [SQX_MARKDOWN_EDITOR_CONTROL_VALUE_ACCESSOR]
}) })
export class MarkdownEditorComponent implements ControlValueAccessor, AfterViewInit { export class MarkdownEditorComponent implements ControlValueAccessor, AfterViewInit {
private changeCallback: (value: any) => void = NOOP; private onChange = (v: any) => { /* NOOP */ };
private touchedCallback: () => void = NOOP; private onTouched = () => { /* NOOP */ };
private simplemde: any; private simplemde: any;
private value: any; private value: string;
private isDisabled = false; private isDisabled = false;
@ViewChild('editor') @ViewChild('editor')
@ -48,11 +48,11 @@ export class MarkdownEditorComponent implements ControlValueAccessor, AfterViewI
this.resourceLoader.loadStyle('https://cdn.jsdelivr.net/simplemde/latest/simplemde.min.css'); this.resourceLoader.loadStyle('https://cdn.jsdelivr.net/simplemde/latest/simplemde.min.css');
} }
public writeValue(value: any) { public writeValue(value: string) {
this.value = value; this.value = Types.isString(value) ? value : '';
if (this.simplemde) { if (this.simplemde) {
this.simplemde.value(this.value || ''); this.simplemde.value(this.value);
} }
} }
@ -65,11 +65,11 @@ export class MarkdownEditorComponent implements ControlValueAccessor, AfterViewI
} }
public registerOnChange(fn: any) { public registerOnChange(fn: any) {
this.changeCallback = fn; this.onChange = fn;
} }
public registerOnTouched(fn: any) { public registerOnTouched(fn: any) {
this.touchedCallback = fn; this.onTouched = fn;
} }
public ngAfterViewInit() { public ngAfterViewInit() {
@ -84,12 +84,12 @@ export class MarkdownEditorComponent implements ControlValueAccessor, AfterViewI
if (this.value !== value) { if (this.value !== value) {
this.value = value; this.value = value;
this.changeCallback(value); this.onChange(value);
} }
}); });
this.simplemde.codemirror.on('blur', () => { this.simplemde.codemirror.on('blur', () => {
this.touchedCallback(); this.onTouched();
}); });
this.simplemde.codemirror.on('refresh', () => { this.simplemde.codemirror.on('refresh', () => {

4
src/Squidex/app/framework/angular/modal-view.directive.ts

@ -16,8 +16,8 @@ import { RootViewService } from './../services/root-view.service';
selector: '[sqxModalView]' selector: '[sqxModalView]'
}) })
export class ModalViewDirective implements OnChanges, OnDestroy { export class ModalViewDirective implements OnChanges, OnDestroy {
private subscription: Subscription | null; private subscription: Subscription | null = null;
private clickHandler: Function | null; private clickHandler: Function | null = null;
private renderedView: EmbeddedViewRef<any> | null = null; private renderedView: EmbeddedViewRef<any> | null = null;
@Input('sqxModalView') @Input('sqxModalView')

2
src/Squidex/app/framework/angular/panel-container.directive.ts

@ -49,7 +49,7 @@ export class PanelContainerDirective implements AfterViewInit, OnDestroy {
} }
public invalidate(params?: { force: boolean, resize: boolean }) { public invalidate(params?: { force: boolean, resize: boolean }) {
this.isInit = this.isInit || (params && params.force); this.isInit = this.isInit || (params && params.force) === true;
if (!this.isInit) { if (!this.isInit) {
return; return;

24
src/Squidex/app/framework/angular/rich-editor.component.ts

@ -8,12 +8,12 @@
import { AfterViewInit, Component, forwardRef, ElementRef, OnDestroy, ViewChild } from '@angular/core'; import { AfterViewInit, Component, forwardRef, ElementRef, OnDestroy, ViewChild } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { Types } from './../utils/types';
import { ResourceLoaderService } from './../services/resource-loader.service'; import { ResourceLoaderService } from './../services/resource-loader.service';
declare var tinymce: any; declare var tinymce: any;
const NOOP = () => { /* NOOP */ };
export const SQX_RICH_EDITOR_CONTROL_VALUE_ACCESSOR: any = { export const SQX_RICH_EDITOR_CONTROL_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => RichEditorComponent), multi: true provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => RichEditorComponent), multi: true
}; };
@ -25,10 +25,10 @@ export const SQX_RICH_EDITOR_CONTROL_VALUE_ACCESSOR: any = {
providers: [SQX_RICH_EDITOR_CONTROL_VALUE_ACCESSOR] providers: [SQX_RICH_EDITOR_CONTROL_VALUE_ACCESSOR]
}) })
export class RichEditorComponent implements ControlValueAccessor, AfterViewInit, OnDestroy { export class RichEditorComponent implements ControlValueAccessor, AfterViewInit, OnDestroy {
private changeCallback: (value: any) => void = NOOP; private onChange = (v: any) => { /* NOOP */ };
private touchedCallback: () => void = NOOP; private onTouched = () => { /* NOOP */ };
private tinyEditor: any; private tinyEditor: any;
private value: any; private value: string;
private isDisabled = false; private isDisabled = false;
@ViewChild('editor') @ViewChild('editor')
@ -39,11 +39,11 @@ export class RichEditorComponent implements ControlValueAccessor, AfterViewInit,
) { ) {
} }
public writeValue(value: any) { public writeValue(value: string) {
this.value = value; this.value = Types.isString(value) ? value : '';
if (this.tinyEditor) { if (this.tinyEditor) {
this.tinyEditor.setContent(value || ''); this.tinyEditor.setContent(this.value);
} }
} }
@ -56,11 +56,11 @@ export class RichEditorComponent implements ControlValueAccessor, AfterViewInit,
} }
public registerOnChange(fn: any) { public registerOnChange(fn: any) {
this.changeCallback = fn; this.onChange = fn;
} }
public registerOnTouched(fn: any) { public registerOnTouched(fn: any) {
this.touchedCallback = fn; this.onTouched = fn;
} }
public ngAfterViewInit() { public ngAfterViewInit() {
@ -78,12 +78,12 @@ export class RichEditorComponent implements ControlValueAccessor, AfterViewInit,
if (this.value !== value) { if (this.value !== value) {
this.value = value; this.value = value;
self.changeCallback(value); self.onChange(value);
} }
}); });
self.tinyEditor.on('blur', () => { self.tinyEditor.on('blur', () => {
self.touchedCallback(); self.onTouched();
}); });
setTimeout(() => { setTimeout(() => {

4
src/Squidex/app/framework/angular/router-utils.ts

@ -8,7 +8,7 @@
import { ActivatedRoute, ActivatedRouteSnapshot, Data, Params } from '@angular/router'; import { ActivatedRoute, ActivatedRouteSnapshot, Data, Params } from '@angular/router';
export function allData(value: ActivatedRouteSnapshot | ActivatedRoute): Data { export function allData(value: ActivatedRouteSnapshot | ActivatedRoute): Data {
let snapshot: ActivatedRouteSnapshot = value['snapshot'] || value; let snapshot: ActivatedRouteSnapshot | null = value['snapshot'] || value;
const result: { [key: string]: any } = { }; const result: { [key: string]: any } = { };
@ -25,7 +25,7 @@ export function allData(value: ActivatedRouteSnapshot | ActivatedRoute): Data {
return result; return result;
} }
export function allParams(value: ActivatedRouteSnapshot | ActivatedRoute): Params { export function allParams(value: ActivatedRouteSnapshot | ActivatedRoute): Params {
let snapshot: ActivatedRouteSnapshot = value['snapshot'] || value; let snapshot: ActivatedRouteSnapshot | null = value['snapshot'] || value;
const result: { [key: string]: any } = { }; const result: { [key: string]: any } = { };

22
src/Squidex/app/framework/angular/slider.component.ts

@ -8,7 +8,7 @@
import { Component, ElementRef, forwardRef, Input, Renderer, ViewChild } from '@angular/core'; import { Component, ElementRef, forwardRef, Input, Renderer, ViewChild } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
const NOOP = () => { /* NOOP */ }; import { Types } from './../utils/types';
export const SQX_SLIDER_CONTROL_VALUE_ACCESSOR: any = { export const SQX_SLIDER_CONTROL_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => SliderComponent), multi: true provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => SliderComponent), multi: true
@ -21,10 +21,10 @@ export const SQX_SLIDER_CONTROL_VALUE_ACCESSOR: any = {
providers: [SQX_SLIDER_CONTROL_VALUE_ACCESSOR] providers: [SQX_SLIDER_CONTROL_VALUE_ACCESSOR]
}) })
export class SliderComponent implements ControlValueAccessor { export class SliderComponent implements ControlValueAccessor {
private changeCallback: (value: any) => void = NOOP; private onChange = (v: any) => { /* NOOP */ };
private touchedCallback: () => void = NOOP; private onTouched = () => { /* NOOP */ };
private mouseMoveSubscription: Function | null; private mouseMoveSubscription: Function | null = null;
private mouseUpSubscription: Function | null; private mouseUpSubscription: Function | null = null;
private centerStartOffset = 0; private centerStartOffset = 0;
private startValue: number; private startValue: number;
private lastValue: number; private lastValue: number;
@ -50,8 +50,8 @@ export class SliderComponent implements ControlValueAccessor {
constructor(private readonly renderer: Renderer) { } constructor(private readonly renderer: Renderer) { }
public writeValue(value: any) { public writeValue(value: number) {
this.lastValue = this.value = value; this.lastValue = this.value = Types.isNumber(value) ? value : 0;
this.updateThumbPosition(); this.updateThumbPosition();
} }
@ -61,11 +61,11 @@ export class SliderComponent implements ControlValueAccessor {
} }
public registerOnChange(fn: any) { public registerOnChange(fn: any) {
this.changeCallback = fn; this.onChange = fn;
} }
public registerOnTouched(fn: any) { public registerOnTouched(fn: any) {
this.touchedCallback = fn; this.onTouched = fn;
} }
public onBarMouseClick(event: MouseEvent): boolean { public onBarMouseClick(event: MouseEvent): boolean {
@ -157,14 +157,14 @@ export class SliderComponent implements ControlValueAccessor {
} }
private updateTouched() { private updateTouched() {
this.touchedCallback(); this.onTouched();
} }
private updateValue() { private updateValue() {
if (this.lastValue !== this.value) { if (this.lastValue !== this.value) {
this.lastValue = this.value; this.lastValue = this.value;
this.changeCallback(this.value); this.onChange(this.value);
} }
} }

37
src/Squidex/app/framework/angular/stars.component.ts

@ -8,7 +8,7 @@
import { Component, forwardRef, Input } from '@angular/core'; import { Component, forwardRef, Input } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
const NOOP = () => { /* NOOP */ }; import { Types } from './../utils/types';
export const SQX_STARS_CONTROL_VALUE_ACCESSOR: any = { export const SQX_STARS_CONTROL_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => StarsComponent), multi: true provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => StarsComponent), multi: true
@ -21,19 +21,15 @@ export const SQX_STARS_CONTROL_VALUE_ACCESSOR: any = {
providers: [SQX_STARS_CONTROL_VALUE_ACCESSOR] providers: [SQX_STARS_CONTROL_VALUE_ACCESSOR]
}) })
export class StarsComponent implements ControlValueAccessor { export class StarsComponent implements ControlValueAccessor {
private changeCallback: (value: any) => void = NOOP; private onChange = (v: any) => { /* NOOP */ };
private touchedCallback: () => void = NOOP; private onTouched = () => { /* NOOP */ };
private maximumStarsValue = 5; private maximumStarsValue = 5;
@Input() @Input()
public set maximumStars(value: any) { public set maximumStars(value: number) {
value = value || 5; const maxStars: number = Types.isNumber(value) ? value : 5;
if (!(typeof value === 'number')) { if (this.maximumStarsValue !== maxStars) {
value = 5;
}
if (this.maximumStarsValue !== value) {
this.maximumStarsValue = value; this.maximumStarsValue = value;
this.starsArray = []; this.starsArray = [];
@ -55,8 +51,13 @@ export class StarsComponent implements ControlValueAccessor {
public value: number | null = 1; public value: number | null = 1;
public writeValue(value: any) { public writeValue(value: number | null | undefined) {
this.value = this.stars = value; if (Types.isNumber(value)) {
this.value = this.stars = value!;
} else {
this.value = null;
this.stars = 0;
}
} }
public setDisabledState(isDisabled: boolean): void { public setDisabledState(isDisabled: boolean): void {
@ -64,11 +65,11 @@ export class StarsComponent implements ControlValueAccessor {
} }
public registerOnChange(fn: any) { public registerOnChange(fn: any) {
this.changeCallback = fn; this.onChange = fn;
} }
public registerOnTouched(fn: any) { public registerOnTouched(fn: any) {
this.touchedCallback = fn; this.onTouched = fn;
} }
public setPreview(value: number) { public setPreview(value: number) {
@ -96,8 +97,8 @@ export class StarsComponent implements ControlValueAccessor {
this.value = null; this.value = null;
this.stars = 0; this.stars = 0;
this.changeCallback(this.value); this.onChange(this.value);
this.touchedCallback(); this.onTouched();
} }
return false; return false;
@ -111,8 +112,8 @@ export class StarsComponent implements ControlValueAccessor {
if (this.value !== value) { if (this.value !== value) {
this.value = this.stars = value; this.value = this.stars = value;
this.changeCallback(this.value); this.onChange(this.value);
this.touchedCallback(); this.onTouched();
} }
return false; return false;

56
src/Squidex/app/framework/angular/tag-editor.component.ts

@ -8,40 +8,54 @@
import { Component, forwardRef, Input } from '@angular/core'; import { Component, forwardRef, Input } from '@angular/core';
import { ControlValueAccessor, FormControl, NG_VALUE_ACCESSOR } from '@angular/forms'; import { ControlValueAccessor, FormControl, NG_VALUE_ACCESSOR } from '@angular/forms';
import { Types } from './../utils/types';
const KEY_ENTER = 13; const KEY_ENTER = 13;
const NOOP = () => { /* NOOP */ };
export interface Converter { export interface Converter {
convert(input: string): any; convert(input: string): any;
isValid(input: string): boolean; isValidInput(input: string): boolean;
isValidValue(value: any): boolean;
} }
export class IntConverter implements Converter { export class IntConverter implements Converter {
public isValid(input: string): boolean { public isValidInput(input: string): boolean {
return !!parseInt(input, 10) || input === '0'; return !!parseInt(input, 10) || input === '0';
} }
public isValidValue(value: any): boolean {
return Types.isNumber(value);
}
public convert(input: string): any { public convert(input: string): any {
return parseInt(input, 10) || 0; return parseInt(input, 10) || 0;
} }
} }
export class FloatConverter implements Converter { export class FloatConverter implements Converter {
public isValid(input: string): boolean { public isValidInput(input: string): boolean {
return !!parseFloat(input) || input === '0'; return !!parseFloat(input) || input === '0';
} }
public isValidValue(value: any): boolean {
return Types.isNumber(value);
}
public convert(input: string): any { public convert(input: string): any {
return parseFloat(input) || 0; return parseFloat(input) || 0;
} }
} }
export class NoopConverter implements Converter { export class StringConverter implements Converter {
public isValid(input: string): boolean { public isValidInput(input: string): boolean {
return input.trim().length > 0; return input.trim().length > 0;
} }
public isValidValue(value: any): boolean {
return Types.isString(value);
}
public convert(input: string): any { public convert(input: string): any {
return input.trim(); return input.trim();
} }
@ -58,11 +72,11 @@ export const SQX_TAG_EDITOR_CONTROL_VALUE_ACCESSOR: any = {
providers: [SQX_TAG_EDITOR_CONTROL_VALUE_ACCESSOR] providers: [SQX_TAG_EDITOR_CONTROL_VALUE_ACCESSOR]
}) })
export class TagEditorComponent implements ControlValueAccessor { export class TagEditorComponent implements ControlValueAccessor {
private changeCallback: (value: any) => void = NOOP; private onChange = (v: any) => { /* NOOP */ };
private touchedCallback: () => void = NOOP; private onTouched = () => { /* NOOP */ };
@Input() @Input()
public converter: Converter = new NoopConverter(); public converter: Converter = new StringConverter();
@Input() @Input()
public useDefaultValue = true; public useDefaultValue = true;
@ -74,10 +88,10 @@ export class TagEditorComponent implements ControlValueAccessor {
public addInput = new FormControl(); public addInput = new FormControl();
public writeValue(value: any) { public writeValue(value: any[]) {
this.addInput.setValue(''); this.resetForm();
if (Array.isArray(value)) { if (this.converter && Types.isArrayOf(value, v => this.converter.isValidValue(v))) {
this.items = value; this.items = value;
} else { } else {
this.items = []; this.items = [];
@ -93,11 +107,11 @@ export class TagEditorComponent implements ControlValueAccessor {
} }
public registerOnChange(fn: any) { public registerOnChange(fn: any) {
this.changeCallback = fn; this.onChange = fn;
} }
public registerOnTouched(fn: any) { public registerOnTouched(fn: any) {
this.touchedCallback = fn; this.onTouched = fn;
} }
public remove(index: number) { public remove(index: number) {
@ -105,18 +119,22 @@ export class TagEditorComponent implements ControlValueAccessor {
} }
public markTouched() { public markTouched() {
this.touchedCallback(); this.onTouched();
}
private resetForm() {
this.addInput.reset();
} }
public onKeyDown(event: KeyboardEvent) { public onKeyDown(event: KeyboardEvent) {
if (event.keyCode === KEY_ENTER) { if (event.keyCode === KEY_ENTER) {
const value = <string>this.addInput.value; const value = <string>this.addInput.value;
if (this.converter.isValid(value)) { if (this.converter.isValidInput(value)) {
const converted = this.converter.convert(value); const converted = this.converter.convert(value);
this.updateItems([...this.items, converted]); this.updateItems([...this.items, converted]);
this.addInput.reset(); this.resetForm();
return false; return false;
} }
} }
@ -128,9 +146,9 @@ export class TagEditorComponent implements ControlValueAccessor {
this.items = items; this.items = items;
if (items.length === 0 && this.useDefaultValue) { if (items.length === 0 && this.useDefaultValue) {
this.changeCallback(undefined); this.onChange(undefined);
} else { } else {
this.changeCallback(this.items); this.onChange(this.items);
} }
} }
} }

21
src/Squidex/app/framework/angular/toggle.component.ts

@ -8,7 +8,7 @@
import { Component, forwardRef } from '@angular/core'; import { Component, forwardRef } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
const NOOP = () => { /* NOOP */ }; import { Types } from './../utils/types';
export const SQX_TOGGLE_CONTROL_VALUE_ACCESSOR: any = { export const SQX_TOGGLE_CONTROL_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => ToggleComponent), multi: true provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => ToggleComponent), multi: true
@ -21,14 +21,14 @@ export const SQX_TOGGLE_CONTROL_VALUE_ACCESSOR: any = {
providers: [SQX_TOGGLE_CONTROL_VALUE_ACCESSOR] providers: [SQX_TOGGLE_CONTROL_VALUE_ACCESSOR]
}) })
export class ToggleComponent implements ControlValueAccessor { export class ToggleComponent implements ControlValueAccessor {
private changeCallback: (value: any) => void = NOOP; private onChange = (v: any) => { /* NOOP */ };
private touchedCallback: () => void = NOOP; private onTouched = () => { /* NOOP */ };
public isChecked: boolean | undefined = undefined; public isChecked: boolean | null = null;
public isDisabled = false; public isDisabled = false;
public writeValue(value: any) { public writeValue(value: boolean | null | undefined) {
this.isChecked = value; this.isChecked = Types.isBoolean(value) ? value! : null;
} }
public setDisabledState(isDisabled: boolean): void { public setDisabledState(isDisabled: boolean): void {
@ -36,20 +36,21 @@ export class ToggleComponent implements ControlValueAccessor {
} }
public registerOnChange(fn: any) { public registerOnChange(fn: any) {
this.changeCallback = fn; this.onChange = fn;
} }
public registerOnTouched(fn: any) { public registerOnTouched(fn: any) {
this.touchedCallback = fn; this.onTouched = fn;
} }
public changeState() { public changeState() {
if (this.isDisabled) { if (this.isDisabled) {
return; return;
} }
this.isChecked = !(this.isChecked === true); this.isChecked = !(this.isChecked === true);
this.changeCallback(this.isChecked); this.onChange(this.isChecked);
this.touchedCallback(); this.onTouched();
} }
} }

39
src/Squidex/app/framework/angular/validators.ts

@ -12,6 +12,7 @@ import {
} from '@angular/forms'; } from '@angular/forms';
import { DateTime } from './../utils/date-time'; import { DateTime } from './../utils/date-time';
import { Types } from './../utils/types';
export module ValidatorsEx { export module ValidatorsEx {
export function pattern(regex: string | RegExp, message?: string): ValidatorFn { export function pattern(regex: string | RegExp, message?: string): ValidatorFn {
@ -30,7 +31,7 @@ export module ValidatorsEx {
regeExp = regex; regeExp = regex;
} }
return (control: AbstractControl): { [key: string]: any } => { return (control: AbstractControl) => {
const n: string = control.value; const n: string = control.value;
if (n == null || n.length === 0) { if (n == null || n.length === 0) {
@ -49,16 +50,16 @@ export module ValidatorsEx {
}; };
} }
export function match(otherControlName: string, message: string) { export function match(otherControlName: string, message: string): ValidatorFn {
let otherControl: AbstractControl = null; let otherControl: AbstractControl | null = null;
return (control: AbstractControl): { [key: string]: any } => { return (control: AbstractControl) => {
if (!control.parent) { if (!control.parent) {
return null; return null;
} }
if (otherControl === null) { if (otherControl === null) {
otherControl = control.parent.get(otherControlName) || undefined; otherControl = control.parent.get(otherControlName);
if (!otherControl) { if (!otherControl) {
throw new Error('matchValidator(): other control is not found in parent group'); throw new Error('matchValidator(): other control is not found in parent group');
@ -77,8 +78,8 @@ export module ValidatorsEx {
}; };
} }
export function validDateTime() { export function validDateTime(): ValidatorFn {
return (control: AbstractControl): { [key: string]: any } => { return (control: AbstractControl) => {
const v: string = control.value; const v: string = control.value;
if (v) { if (v) {
@ -93,32 +94,32 @@ export module ValidatorsEx {
}; };
} }
export function between(minValue?: number, maxValue?: number) { export function between(minValue?: number, maxValue?: number): ValidatorFn {
if (!minValue || !maxValue) { if (!minValue || !maxValue) {
return Validators.nullValidator; return Validators.nullValidator;
} }
return (control: AbstractControl): { [key: string]: any } => { return (control: AbstractControl) => {
const n: number = control.value; const value: number = control.value;
if (typeof n !== 'number') { if (!Types.isNumber(value)) {
return { validnumber: false }; return { validnumber: false };
} else if (minValue && n < minValue) { } else if (minValue && value < minValue) {
return { minvalue: { minValue, actualValue: n } }; return { minvalue: { minValue, actualValue: value } };
} else if (maxValue && n > maxValue) { } else if (maxValue && value > maxValue) {
return { maxvalue: { maxValue, actualValue: n } }; return { maxvalue: { maxValue, actualValue: value } };
} }
return null; return null;
}; };
} }
export function validValues<T>(values: T[]) { export function validValues<T>(values: T[]): ValidatorFn {
if (!values) { if (!values) {
return Validators.nullValidator; return Validators.nullValidator;
} }
return (control: AbstractControl): { [key: string]: any } => { return (control: AbstractControl) => {
const n: T = control.value; const n: T = control.value;
if (values.indexOf(n) < 0) { if (values.indexOf(n) < 0) {
@ -129,8 +130,8 @@ export module ValidatorsEx {
}; };
} }
export function noop() { export function noop(): ValidatorFn {
return (control: AbstractControl): { [key: string]: any } => { return (control: AbstractControl) => {
return null; return null;
}; };
} }

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

@ -71,4 +71,5 @@ export * from './utils/math-helper';
export * from './utils/modal-view'; export * from './utils/modal-view';
export * from './utils/pager'; export * from './utils/pager';
export * from './utils/string-helper'; export * from './utils/string-helper';
export * from './utils/types';
export * from './utils/version'; export * from './utils/version';

2
src/Squidex/app/framework/services/local-cache.service.ts

@ -31,7 +31,7 @@ export class LocalCacheService {
} }
} }
public get<T>(key: string, now?: number): T { public get<T>(key: string, now?: number): T | undefined {
const entry = this.entries[key]; const entry = this.entries[key];
if (entry) { if (entry) {

92
src/Squidex/app/framework/utils/types.spec.ts

@ -0,0 +1,92 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Types } from './../';
describe('Types', () => {
it('should make string check', () => {
expect(Types.isString('')).toBeTruthy();
expect(Types.isString('string')).toBeTruthy();
expect(Types.isString(false)).toBeFalsy();
});
it('should make number check', () => {
expect(Types.isNumber(0)).toBeTruthy();
expect(Types.isNumber(1)).toBeTruthy();
expect(Types.isNumber(NaN)).toBeFalsy();
expect(Types.isNumber(Infinity)).toBeFalsy();
expect(Types.isNumber(false)).toBeFalsy();
});
it('should make boolean check', () => {
expect(Types.isBoolean(true)).toBeTruthy();
expect(Types.isBoolean(false)).toBeTruthy();
expect(Types.isBoolean(0)).toBeFalsy();
expect(Types.isBoolean(1)).toBeFalsy();
});
it('should make number array check', () => {
expect(Types.isArrayOfNumber([])).toBeTruthy();
expect(Types.isArrayOfNumber([0, 1])).toBeTruthy();
expect(Types.isArrayOfNumber(['0', 1])).toBeFalsy();
});
it('should make string array check', () => {
expect(Types.isArrayOfString([])).toBeTruthy();
expect(Types.isArrayOfString(['0', '1'])).toBeTruthy();
expect(Types.isArrayOfString(['0', 1])).toBeFalsy();
});
it('should make array check', () => {
expect(Types.isArray([])).toBeTruthy();
expect(Types.isArray([0])).toBeTruthy();
expect(Types.isArray({})).toBeFalsy();
});
it('should make object check', () => {
expect(Types.isObject({})).toBeTruthy();
expect(Types.isObject({ v: 1 })).toBeTruthy();
expect(Types.isObject([])).toBeFalsy();
});
it('should make RegExp check', () => {
expect(Types.isRegExp(/[.*]/)).toBeTruthy();
expect(Types.isRegExp('/[.*]/')).toBeFalsy();
});
it('should make Date check', () => {
expect(Types.isDate(new Date())).toBeTruthy();
expect(Types.isDate(new Date().getDate())).toBeFalsy();
});
it('should make undefined check', () => {
expect(Types.isUndefined(undefined)).toBeTruthy();
expect(Types.isUndefined(null)).toBeFalsy();
});
it('should make null check', () => {
expect(Types.isNull(null)).toBeTruthy();
expect(Types.isNull(undefined)).toBeFalsy();
});
it('should make function check', () => {
expect(Types.isFunction(() => { /* NOOP */ })).toBeTruthy();
expect(Types.isFunction([])).toBeFalsy();
});
});

70
src/Squidex/app/framework/utils/types.ts

@ -0,0 +1,70 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
export module Types {
export function isString(value: any): boolean {
return typeof value === 'string' || value instanceof String;
}
export function isNumber(value: any): boolean {
return typeof value === 'number' && isFinite(value);
}
export function isArray(value: any): boolean {
return Array.isArray(value);
}
export function isFunction(value: any): boolean {
return typeof value === 'function';
}
export function isObject(value: any): boolean {
return value && typeof value === 'object' && value.constructor === Object;
}
export function isBoolean(value: any): boolean {
return typeof value === 'boolean';
};
export function isNull(value: any): boolean {
return value === null;
}
export function isUndefined(value: any): boolean {
return typeof value === 'undefined';
}
export function isRegExp(value: any): boolean {
return value && typeof value === 'object' && value.constructor === RegExp;
}
export function isDate(value: any): boolean {
return value instanceof Date;
}
export function isArrayOfNumber(value: any): boolean {
return isArrayOf(value, v => isNumber(v));
}
export function isArrayOfString(value: any): boolean {
return isArrayOf(value, v => isString(v));
}
export function isArrayOf(value: any, validator: (v: any) => boolean): boolean {
if (!Array.isArray(value)) {
return false;
}
for (let v of value) {
if (!validator(v)) {
return false;
}
}
return true;
}
}

6
src/Squidex/app/shared/components/asset.component.ts

@ -83,7 +83,7 @@ export class AssetComponent extends AppComponentBase implements OnInit {
if (initFile) { if (initFile) {
this.appNameOnce() this.appNameOnce()
.switchMap(app => this.assetsService.uploadFile(app, initFile, this.authService.user.token)) .switchMap(app => this.assetsService.uploadFile(app, initFile, this.authService.user!.token))
.subscribe(dto => { .subscribe(dto => {
if (dto instanceof AssetDto) { if (dto instanceof AssetDto) {
this.emitLoaded(dto); this.emitLoaded(dto);
@ -104,7 +104,7 @@ export class AssetComponent extends AppComponentBase implements OnInit {
.switchMap(app => this.assetsService.replaceFile(app, this.asset.id, files[0], this.assetVersion)) .switchMap(app => this.assetsService.replaceFile(app, this.asset.id, files[0], this.assetVersion))
.subscribe(dto => { .subscribe(dto => {
if (dto instanceof AssetReplacedDto) { if (dto instanceof AssetReplacedDto) {
this.updateAsset(this.asset.update(dto, this.authService.user.token), true); this.updateAsset(this.asset.update(dto, this.authService.user!.token), true);
} else { } else {
this.setProgress(dto); this.setProgress(dto);
} }
@ -126,7 +126,7 @@ export class AssetComponent extends AppComponentBase implements OnInit {
this.appNameOnce() this.appNameOnce()
.switchMap(app => this.assetsService.putAsset(app, this.asset.id, requestDto, this.assetVersion)) .switchMap(app => this.assetsService.putAsset(app, this.asset.id, requestDto, this.assetVersion))
.subscribe(() => { .subscribe(() => {
this.updateAsset(this.asset.rename(requestDto.fileName, this.authService.user.token), true); this.updateAsset(this.asset.rename(requestDto.fileName, this.authService.user!.token), true);
this.resetRenameForm(); this.resetRenameForm();
}, error => { }, error => {
this.notifyError(error); this.notifyError(error);

4
src/Squidex/app/shared/guards/resolve-app-languages.guard.ts

@ -14,14 +14,14 @@ import { allParams } from 'framework';
import { AppLanguageDto, AppLanguagesService } from './../services/app-languages.service'; import { AppLanguageDto, AppLanguagesService } from './../services/app-languages.service';
@Injectable() @Injectable()
export class ResolveAppLanguagesGuard implements Resolve<AppLanguageDto[]> { export class ResolveAppLanguagesGuard implements Resolve<AppLanguageDto[] | null> {
constructor( constructor(
private readonly appLanguagesService: AppLanguagesService, private readonly appLanguagesService: AppLanguagesService,
private readonly router: Router private readonly router: Router
) { ) {
} }
public resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<AppLanguageDto[]> { public resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<AppLanguageDto[] | null> {
const params = allParams(route); const params = allParams(route);
const appName = params['appName']; const appName = params['appName'];

4
src/Squidex/app/shared/guards/resolve-content.guard.ts

@ -14,14 +14,14 @@ import { allParams } from 'framework';
import { ContentDto, ContentsService } from './../services/contents.service'; import { ContentDto, ContentsService } from './../services/contents.service';
@Injectable() @Injectable()
export class ResolveContentGuard implements Resolve<ContentDto> { export class ResolveContentGuard implements Resolve<ContentDto | null> {
constructor( constructor(
private readonly contentsService: ContentsService, private readonly contentsService: ContentsService,
private readonly router: Router private readonly router: Router
) { ) {
} }
public resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<ContentDto> { public resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<ContentDto | null> {
const params = allParams(route); const params = allParams(route);
const appName = params['appName']; const appName = params['appName'];

4
src/Squidex/app/shared/guards/resolve-user.guard.ts

@ -14,14 +14,14 @@ import { allParams } from 'framework';
import { UserDto, UserManagementService } from './../services/users.service'; import { UserDto, UserManagementService } from './../services/users.service';
@Injectable() @Injectable()
export class ResolveUserGuard implements Resolve<UserDto> { export class ResolveUserGuard implements Resolve<UserDto | null> {
constructor( constructor(
private readonly userManagementService: UserManagementService, private readonly userManagementService: UserManagementService,
private readonly router: Router private readonly router: Router
) { ) {
} }
public resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<UserDto> { public resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<UserDto | null> {
const params = allParams(route); const params = allParams(route);
const userId = params['userId']; const userId = params['userId'];

2
src/Squidex/app/shared/interceptors/auth.interceptor.spec.ts

@ -18,7 +18,7 @@ import {
} from './../'; } from './../';
describe('AuthInterceptor', () => { describe('AuthInterceptor', () => {
let authService: IMock<AuthService> = null; let authService: IMock<AuthService>;
beforeEach(() => { beforeEach(() => {
authService = Mock.ofType(AuthService); authService = Mock.ofType(AuthService);

2
src/Squidex/app/shared/interceptors/auth.interceptor.ts

@ -32,7 +32,7 @@ export class AuthInterceptor implements HttpInterceptor {
} }
} }
private makeRequest(req: HttpRequest<any>, next: HttpHandler, user: Profile, renew = false): Observable<HttpEvent<any>> { private makeRequest(req: HttpRequest<any>, next: HttpHandler, user: Profile | null, renew = false): Observable<HttpEvent<any>> {
const token = user ? user.authToken : ''; const token = user ? user.authToken : '';
const authReq = req.clone({ const authReq = req.clone({

6
src/Squidex/app/shared/services/app-clients.service.ts

@ -84,7 +84,7 @@ export class AppClientsService {
.pretifyError('Failed to load clients. Please reload.'); .pretifyError('Failed to load clients. Please reload.');
} }
public postClient(appName: string, dto: CreateAppClientDto, version?: Version): Observable<AppClientDto> { public postClient(appName: string, dto: CreateAppClientDto, version: Version): Observable<AppClientDto> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/clients`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/clients`);
return HTTP.postVersioned(this.http, url, dto, version) return HTTP.postVersioned(this.http, url, dto, version)
@ -98,14 +98,14 @@ export class AppClientsService {
.pretifyError('Failed to add client. Please reload.'); .pretifyError('Failed to add client. Please reload.');
} }
public updateClient(appName: string, id: string, dto: UpdateAppClientDto, version?: Version): Observable<any> { public updateClient(appName: string, id: string, dto: UpdateAppClientDto, version: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/clients/${id}`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/clients/${id}`);
return HTTP.putVersioned(this.http, url, dto, version) return HTTP.putVersioned(this.http, url, dto, version)
.pretifyError('Failed to revoke client. Please reload.'); .pretifyError('Failed to revoke client. Please reload.');
} }
public deleteClient(appName: string, id: string, version?: Version): Observable<any> { public deleteClient(appName: string, id: string, version: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/clients/${id}`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/clients/${id}`);
return HTTP.deleteVersioned(this.http, url, version) return HTTP.deleteVersioned(this.http, url, version)

4
src/Squidex/app/shared/services/app-contributors.service.ts

@ -63,14 +63,14 @@ export class AppContributorsService {
.pretifyError('Failed to load contributors. Please reload.'); .pretifyError('Failed to load contributors. Please reload.');
} }
public postContributor(appName: string, dto: AppContributorDto, version?: Version): Observable<any> { public postContributor(appName: string, dto: AppContributorDto, version: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/contributors`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/contributors`);
return HTTP.postVersioned(this.http, url, dto, version) return HTTP.postVersioned(this.http, url, dto, version)
.pretifyError('Failed to add contributors. Please reload.'); .pretifyError('Failed to add contributors. Please reload.');
} }
public deleteContributor(appName: string, contributorId: string, version?: Version): Observable<any> { public deleteContributor(appName: string, contributorId: string, version: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/contributors/${contributorId}`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/contributors/${contributorId}`);
return HTTP.deleteVersioned(this.http, url, version) return HTTP.deleteVersioned(this.http, url, version)

6
src/Squidex/app/shared/services/app-languages.service.ts

@ -75,7 +75,7 @@ export class AppLanguagesService {
.pretifyError('Failed to load languages. Please reload.'); .pretifyError('Failed to load languages. Please reload.');
} }
public postLanguages(appName: string, dto: AddAppLanguageDto, version?: Version): Observable<AppLanguageDto> { public postLanguages(appName: string, dto: AddAppLanguageDto, version: Version): Observable<AppLanguageDto> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/languages`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/languages`);
return HTTP.postVersioned(this.http, url, dto, version) return HTTP.postVersioned(this.http, url, dto, version)
@ -90,14 +90,14 @@ export class AppLanguagesService {
.pretifyError('Failed to add language. Please reload.'); .pretifyError('Failed to add language. Please reload.');
} }
public updateLanguage(appName: string, languageCode: string, dto: UpdateAppLanguageDto, version?: Version): Observable<any> { public updateLanguage(appName: string, languageCode: string, dto: UpdateAppLanguageDto, version: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/languages/${languageCode}`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/languages/${languageCode}`);
return HTTP.putVersioned(this.http, url, dto, version) return HTTP.putVersioned(this.http, url, dto, version)
.pretifyError('Failed to change language. Please reload.'); .pretifyError('Failed to change language. Please reload.');
} }
public deleteLanguage(appName: string, languageCode: string, version?: Version): Observable<any> { public deleteLanguage(appName: string, languageCode: string, version: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/languages/${languageCode}`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/languages/${languageCode}`);
return HTTP.deleteVersioned(this.http, url, version) return HTTP.deleteVersioned(this.http, url, version)

19
src/Squidex/app/shared/services/assets.service.spec.ts

@ -24,7 +24,7 @@ describe('AssetDto', () => {
it('should update name property and user info when renaming', () => { it('should update name property and user info when renaming', () => {
const now = DateTime.now(); const now = DateTime.now();
const asset_1 = new AssetDto('1', 'other', 'other', DateTime.today(), DateTime.today(), 'name.png', 'png', 1, 1, 'image/png', false, 1, 1, null); const asset_1 = new AssetDto('1', 'other', 'other', DateTime.today(), DateTime.today(), 'name.png', 'png', 1, 1, 'image/png', false, 1, 1, new Version('1'));
const asset_2 = asset_1.rename('new-name.png', 'me', now); const asset_2 = asset_1.rename('new-name.png', 'me', now);
expect(asset_2.fileName).toEqual('new-name.png'); expect(asset_2.fileName).toEqual('new-name.png');
@ -35,9 +35,9 @@ describe('AssetDto', () => {
it('should update file properties when uploading', () => { it('should update file properties when uploading', () => {
const now = DateTime.now(); const now = DateTime.now();
const update = new AssetReplacedDto(2, 2, 'image/jpeg', true, 2, 2, null); const update = new AssetReplacedDto(2, 2, 'image/jpeg', true, 2, 2, new Version('1'));
const asset_1 = new AssetDto('1', 'other', 'other', DateTime.today(), DateTime.today(), 'name.png', 'png', 1, 1, 'image/png', false, 1, 1, null); const asset_1 = new AssetDto('1', 'other', 'other', DateTime.today(), DateTime.today(), 'name.png', 'png', 1, 1, 'image/png', false, 1, 1, new Version('1'));
const asset_2 = asset_1.update(update, 'me', now); const asset_2 = asset_1.update(update, 'me', now);
expect(asset_2.fileSize).toEqual(2); expect(asset_2.fileSize).toEqual(2);
@ -48,6 +48,7 @@ describe('AssetDto', () => {
expect(asset_2.pixelHeight).toEqual(2); expect(asset_2.pixelHeight).toEqual(2);
expect(asset_2.lastModified).toEqual(now); expect(asset_2.lastModified).toEqual(now);
expect(asset_2.lastModifiedBy).toEqual('me'); expect(asset_2.lastModifiedBy).toEqual('me');
expect(asset_2.version).toEqual(update);
}); });
}); });
@ -160,7 +161,7 @@ describe('AssetsService', () => {
let asset: AssetDto | null = null; let asset: AssetDto | null = null;
assetsService.getAsset('my-app', '123').subscribe(result => { assetsService.getAsset('my-app', '123', version).subscribe(result => {
asset = result; asset = result;
}); });
@ -211,14 +212,14 @@ describe('AssetsService', () => {
let asset: AssetDto | null = null; let asset: AssetDto | null = null;
assetsService.getAsset('my-app', '123').subscribe(result => { assetsService.getAsset('my-app', '123', version).subscribe(result => {
asset = result; asset = result;
}); });
const req = httpMock.expectOne('http://service/p/api/apps/my-app/assets/123'); const req = httpMock.expectOne('http://service/p/api/apps/my-app/assets/123');
expect(req.request.method).toEqual('GET'); expect(req.request.method).toEqual('GET');
expect(req.request.headers.get('If-Match')).toBeNull(); expect(req.request.headers.get('If-Match')).toEqual(version.value);
req.flush({}, { status: 404, statusText: '404' }); req.flush({}, { status: 404, statusText: '404' });
@ -246,7 +247,7 @@ describe('AssetsService', () => {
const req = httpMock.expectOne('http://service/p/api/apps/my-app/assets?mimeTypes=image/png,image/png&take=17&skip=13'); const req = httpMock.expectOne('http://service/p/api/apps/my-app/assets?mimeTypes=image/png,image/png&take=17&skip=13');
expect(req.request.method).toEqual('GET'); expect(req.request.method).toEqual('GET');
expect(req.request.headers.get('If-Match')).toBeNull(); expect(req.request.headers.get('If-Match')).toEqual(version.value);
req.flush({ total: 10, items: [] }); req.flush({ total: 10, items: [] });
})); }));
@ -259,7 +260,7 @@ describe('AssetsService', () => {
const req = httpMock.expectOne('http://service/p/api/apps/my-app/assets?ids=12,23&take=17&skip=13'); const req = httpMock.expectOne('http://service/p/api/apps/my-app/assets?ids=12,23&take=17&skip=13');
expect(req.request.method).toEqual('GET'); expect(req.request.method).toEqual('GET');
expect(req.request.headers.get('If-Match')).toBeNull(); expect(req.request.headers.get('If-Match')).toEqual(version.value);
req.flush({ total: 10, items: [] }); req.flush({ total: 10, items: [] });
})); }));
@ -276,7 +277,7 @@ describe('AssetsService', () => {
const req = httpMock.expectOne('http://service/p/api/apps/my-app/assets'); const req = httpMock.expectOne('http://service/p/api/apps/my-app/assets');
expect(req.request.method).toEqual('POST'); expect(req.request.method).toEqual('POST');
expect(req.request.headers.get('If-Match')).toBeNull(); expect(req.request.headers.get('If-Match')).toEqual(version.value);
req.flush({ req.flush({
id: 'id1', id: 'id1',

23
src/Squidex/app/shared/services/assets.service.ts

@ -163,7 +163,7 @@ export class AssetsService {
return this.http.request(req) return this.http.request(req)
.map(event => { .map(event => {
if (event.type === HttpEventType.UploadProgress) { if (event.type === HttpEventType.UploadProgress) {
const percentDone = Math.round(100 * event.loaded / event.total); const percentDone = event.total ? Math.round(100 * event.loaded / event.total) : 0;
return percentDone; return percentDone;
} else if (event instanceof HttpResponse) { } else if (event instanceof HttpResponse) {
@ -235,20 +235,21 @@ export class AssetsService {
.pretifyError('Failed to load assets. Please reload.'); .pretifyError('Failed to load assets. Please reload.');
} }
public replaceFile(appName: string, id: string, file: File, version?: Version): Observable<number | AssetReplacedDto> { public replaceFile(appName: string, id: string, file: File, version: Version): Observable<number | AssetReplacedDto> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/assets/${id}/content`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/assets/${id}/content`);
const req = new HttpRequest('PUT', url, getFormData(file), { const headers = new HttpHeaders();
headers: new HttpHeaders({
'If-Match': version.value if (version) {
}), headers.set('If-Match', version.value);
reportProgress: true }
});
const req = new HttpRequest('PUT', url, getFormData(file), { headers, reportProgress: true });
return this.http.request(req) return this.http.request(req)
.map(event => { .map(event => {
if (event.type === HttpEventType.UploadProgress) { if (event.type === HttpEventType.UploadProgress) {
const percentDone = Math.round(100 * event.loaded / event.total); const percentDone = event.total ? Math.round(100 * event.loaded / event.total) : 0;
return percentDone; return percentDone;
} else if (event instanceof HttpResponse) { } else if (event instanceof HttpResponse) {
@ -269,7 +270,7 @@ export class AssetsService {
.pretifyError('Failed to replace asset. Please reload.'); .pretifyError('Failed to replace asset. Please reload.');
} }
public deleteAsset(appName: string, id: string, version?: Version): Observable<any> { public deleteAsset(appName: string, id: string, version: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/assets/${id}`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/assets/${id}`);
return HTTP.deleteVersioned(this.http, url, version) return HTTP.deleteVersioned(this.http, url, version)
@ -279,7 +280,7 @@ export class AssetsService {
.pretifyError('Failed to delete asset. Please reload.'); .pretifyError('Failed to delete asset. Please reload.');
} }
public putAsset(appName: string, id: string, dto: UpdateAssetDto, version?: Version): Observable<any> { public putAsset(appName: string, id: string, dto: UpdateAssetDto, version: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/assets/${id}`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/assets/${id}`);
return HTTP.putVersioned(this.http, url, dto, version) return HTTP.putVersioned(this.http, url, dto, version)

18
src/Squidex/app/shared/services/auth.service.ts

@ -39,7 +39,7 @@ export class Profile {
} }
public get authToken(): string { public get authToken(): string {
return `${this.user.token_type} ${this.user.access_token}`; return `${this.user!.token_type} ${this.user.access_token}`;
} }
public get token(): string { public get token(): string {
@ -56,7 +56,7 @@ export class Profile {
export class AuthService { export class AuthService {
private readonly userManager: UserManager; private readonly userManager: UserManager;
private readonly user$ = new ReplaySubject<Profile | null>(1); private readonly user$ = new ReplaySubject<Profile | null>(1);
private currentUser: Profile = null; private currentUser: Profile | null = null;
public get user(): Profile | null { public get user(): Profile | null {
return this.currentUser; return this.currentUser;
@ -150,7 +150,7 @@ export class AuthService {
public loginSilent(): Observable<Profile> { public loginSilent(): Observable<Profile> {
const observable: Observable<Profile> = const observable: Observable<Profile> =
Observable.create((observer: Observer<Profile>) => { Observable.create((observer: Observer<Profile | null>) => {
this.userManager.signinSilent() this.userManager.signinSilent()
.then(x => { .then(x => {
observer.next(this.createProfile(x)); observer.next(this.createProfile(x));
@ -169,13 +169,17 @@ export class AuthService {
.concat(Observable.throw(new Error('Retry limit exceeded.')))); .concat(Observable.throw(new Error('Retry limit exceeded.'))));
} }
private createProfile(user: User) { private createProfile(user: User): Profile {
return user ? new Profile(user) : null; return new Profile(user);
} }
private checkState(promise: Promise<User>) { private checkState(promise: Promise<User | null>) {
promise.then(user => { promise.then(user => {
this.user$.next(this.createProfile(user)); if (user) {
this.user$.next(this.createProfile(user));
} else {
this.user$.next(null);
}
return true; return true;
}, err => { }, err => {

16
src/Squidex/app/shared/services/contents.service.spec.ts

@ -22,7 +22,7 @@ describe('ContentDto', () => {
it('should update data property and user info when updating', () => { it('should update data property and user info when updating', () => {
const now = DateTime.now(); const now = DateTime.now();
const content_1 = new ContentDto('1', false, false, 'other', 'other', DateTime.now(), DateTime.now(), { data: 1 }, null); const content_1 = new ContentDto('1', false, false, 'other', 'other', DateTime.now(), DateTime.now(), { data: 1 }, new Version('1'));
const content_2 = content_1.update({ data: 2 }, 'me', now); const content_2 = content_1.update({ data: 2 }, 'me', now);
expect(content_2.data).toEqual({ data: 2 }); expect(content_2.data).toEqual({ data: 2 });
@ -33,7 +33,7 @@ describe('ContentDto', () => {
it('should update isPublished property and user info when publishing', () => { it('should update isPublished property and user info when publishing', () => {
const now = DateTime.now(); const now = DateTime.now();
const content_1 = new ContentDto('1', false, false, 'other', 'other', DateTime.now(), DateTime.now(), { data: 1 }, null); const content_1 = new ContentDto('1', false, false, 'other', 'other', DateTime.now(), DateTime.now(), { data: 1 }, new Version('1'));
const content_2 = content_1.publish('me', now); const content_2 = content_1.publish('me', now);
expect(content_2.isPublished).toBeTruthy(); expect(content_2.isPublished).toBeTruthy();
@ -44,7 +44,7 @@ describe('ContentDto', () => {
it('should update isPublished property and user info when unpublishing', () => { it('should update isPublished property and user info when unpublishing', () => {
const now = DateTime.now(); const now = DateTime.now();
const content_1 = new ContentDto('1', true, false, 'other', 'other', DateTime.now(), DateTime.now(), { data: 1 }, null); const content_1 = new ContentDto('1', true, false, 'other', 'other', DateTime.now(), DateTime.now(), { data: 1 }, new Version('1'));
const content_2 = content_1.unpublish('me', now); const content_2 = content_1.unpublish('me', now);
expect(content_2.isPublished).toBeFalsy(); expect(content_2.isPublished).toBeFalsy();
@ -55,7 +55,7 @@ describe('ContentDto', () => {
it('should update data property when setting data', () => { it('should update data property when setting data', () => {
const newData = {}; const newData = {};
const content_1 = new ContentDto('1', true, false, 'other', 'other', DateTime.now(), DateTime.now(), { data: 1 }, null); const content_1 = new ContentDto('1', true, false, 'other', 'other', DateTime.now(), DateTime.now(), { data: 1 }, new Version('1'));
const content_2 = content_1.setData(newData); const content_2 = content_1.setData(newData);
expect(content_2.data).toBe(newData); expect(content_2.data).toBe(newData);
@ -140,7 +140,7 @@ describe('ContentsService', () => {
it('should append query to get request as search', it('should append query to get request as search',
inject([ContentsService, HttpTestingController], (contentsService: ContentsService, httpMock: HttpTestingController) => { inject([ContentsService, HttpTestingController], (contentsService: ContentsService, httpMock: HttpTestingController) => {
let contents: ContentsDto | null; let contents: ContentsDto | null = null;
contentsService.getContents('my-app', 'my-schema', 17, 13, 'my-query').subscribe(result => { contentsService.getContents('my-app', 'my-schema', 17, 13, 'my-query').subscribe(result => {
contents = result; contents = result;
@ -157,9 +157,9 @@ describe('ContentsService', () => {
it('should append ids to get request with ids', it('should append ids to get request with ids',
inject([ContentsService, HttpTestingController], (contentsService: ContentsService, httpMock: HttpTestingController) => { inject([ContentsService, HttpTestingController], (contentsService: ContentsService, httpMock: HttpTestingController) => {
let contents: ContentsDto | null; let contents: ContentsDto | null = null;
contentsService.getContents('my-app', 'my-schema', 17, 13, null, ['id1', 'id2']).subscribe(result => { contentsService.getContents('my-app', 'my-schema', 17, 13, undefined, ['id1', 'id2']).subscribe(result => {
contents = result; contents = result;
}); });
@ -174,7 +174,7 @@ describe('ContentsService', () => {
it('should append query to get request as plain query string', it('should append query to get request as plain query string',
inject([ContentsService, HttpTestingController], (contentsService: ContentsService, httpMock: HttpTestingController) => { inject([ContentsService, HttpTestingController], (contentsService: ContentsService, httpMock: HttpTestingController) => {
let contents: ContentsDto | null; let contents: ContentsDto | null = null;
contentsService.getContents('my-app', 'my-schema', 17, 13, '$filter=my-filter').subscribe(result => { contentsService.getContents('my-app', 'my-schema', 17, 13, '$filter=my-filter').subscribe(result => {
contents = result; contents = result;

14
src/Squidex/app/shared/services/contents.service.ts

@ -176,7 +176,7 @@ export class ContentsService {
.pretifyError('Failed to load content. Please reload.'); .pretifyError('Failed to load content. Please reload.');
} }
public postContent(appName: string, schemaName: string, dto: any, publish: boolean, version?: Version): Observable<ContentDto> { public postContent(appName: string, schemaName: string, dto: any, publish: boolean, version: Version): Observable<ContentDto> {
const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}?publish=${publish}`); const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}?publish=${publish}`);
return HTTP.postVersioned(this.http, url, dto, version) return HTTP.postVersioned(this.http, url, dto, version)
@ -197,7 +197,7 @@ export class ContentsService {
.pretifyError('Failed to create content. Please reload.'); .pretifyError('Failed to create content. Please reload.');
} }
public putContent(appName: string, schemaName: string, id: string, dto: any, version?: Version): Observable<any> { public putContent(appName: string, schemaName: string, id: string, dto: any, version: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}/${id}`); const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}/${id}`);
return HTTP.putVersioned(this.http, url, dto, version) return HTTP.putVersioned(this.http, url, dto, version)
@ -207,8 +207,8 @@ export class ContentsService {
.pretifyError('Failed to update content. Please reload.'); .pretifyError('Failed to update content. Please reload.');
} }
public deleteContent(appName: string, schemaName: string, id: string, version?: Version): Observable<any> { public deleteContent(appName: string, schemaName: string, id: string, version: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}/${id}`); const url = this.apiUrl.buildUrl(`/api/coentent/${appName}/${schemaName}/${id}`);
return HTTP.deleteVersioned(this.http, url, version) return HTTP.deleteVersioned(this.http, url, version)
.do(() => { .do(() => {
@ -217,21 +217,21 @@ export class ContentsService {
.pretifyError('Failed to delete content. Please reload.'); .pretifyError('Failed to delete content. Please reload.');
} }
public getVersionData(appName: string, schemaName: string, id: string, version?: Version): Observable<any> { public getVersionData(appName: string, schemaName: string, id: string, version: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}/${id}/${version.value}`); const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}/${id}/${version.value}`);
return HTTP.getVersioned(this.http, url, version) return HTTP.getVersioned(this.http, url, version)
.pretifyError('Failed to load data. Please reload.'); .pretifyError('Failed to load data. Please reload.');
} }
public publishContent(appName: string, schemaName: string, id: string, version?: Version): Observable<any> { public publishContent(appName: string, schemaName: string, id: string, version: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}/${id}/publish`); const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}/${id}/publish`);
return HTTP.putVersioned(this.http, url, {}, version) return HTTP.putVersioned(this.http, url, {}, version)
.pretifyError('Failed to publish content. Please reload.'); .pretifyError('Failed to publish content. Please reload.');
} }
public unpublishContent(appName: string, schemaName: string, id: string, version?: Version): Observable<any> { public unpublishContent(appName: string, schemaName: string, id: string, version: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}/${id}/unpublish`); const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}/${id}/unpublish`);
return HTTP.putVersioned(this.http, url, {}, version) return HTTP.putVersioned(this.http, url, {}, version)

2
src/Squidex/app/shared/services/plans.service.ts

@ -85,7 +85,7 @@ export class PlansService {
.pretifyError('Failed to load plans. Please reload.'); .pretifyError('Failed to load plans. Please reload.');
} }
public putPlan(appName: string, dto: ChangePlanDto, version?: Version): Observable<PlanChangedDto> { public putPlan(appName: string, dto: ChangePlanDto, version: Version): Observable<PlanChangedDto> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/plan`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/plan`);
return HTTP.putVersioned(this.http, url, dto, version) return HTTP.putVersioned(this.http, url, dto, version)

32
src/Squidex/app/shared/services/schemas.service.spec.ts

@ -27,12 +27,12 @@ import {
} from './../'; } from './../';
describe('SchemaDto', () => { describe('SchemaDto', () => {
const properties = new SchemaPropertiesDto('Name', null); const properties = new SchemaPropertiesDto('Name');
it('should update isPublished property and user info when publishing', () => { it('should update isPublished property and user info when publishing', () => {
const now = DateTime.now(); const now = DateTime.now();
const schema_1 = new SchemaDto('1', 'name', properties, false, 'other', 'other', DateTime.now(), DateTime.now(), null); const schema_1 = new SchemaDto('1', 'name', properties, false, 'other', 'other', DateTime.now(), DateTime.now(), new Version('1'));
const schema_2 = schema_1.publish('me', now); const schema_2 = schema_1.publish('me', now);
expect(schema_2.isPublished).toBeTruthy(); expect(schema_2.isPublished).toBeTruthy();
@ -43,7 +43,7 @@ describe('SchemaDto', () => {
it('should update isPublished property and user info when unpublishing', () => { it('should update isPublished property and user info when unpublishing', () => {
const now = DateTime.now(); const now = DateTime.now();
const schema_1 = new SchemaDto('1', 'name', properties, true, 'other', 'other', DateTime.now(), DateTime.now(), null); const schema_1 = new SchemaDto('1', 'name', properties, true, 'other', 'other', DateTime.now(), DateTime.now(), new Version('1'));
const schema_2 = schema_1.unpublish('me', now); const schema_2 = schema_1.unpublish('me', now);
expect(schema_2.isPublished).toBeFalsy(); expect(schema_2.isPublished).toBeFalsy();
@ -52,11 +52,11 @@ describe('SchemaDto', () => {
}); });
it('should update properties property and user info when updating', () => { it('should update properties property and user info when updating', () => {
const newProperties = new SchemaPropertiesDto('New Name', null); const newProperties = new SchemaPropertiesDto('New Name');
const now = DateTime.now(); const now = DateTime.now();
const schema_1 = new SchemaDto('1', 'name', properties, false, 'other', 'other', DateTime.now(), DateTime.now(), null); const schema_1 = new SchemaDto('1', 'name', properties, false, 'other', 'other', DateTime.now(), DateTime.now(), new Version('1'));
const schema_2 = schema_1.update(newProperties, 'me', now); const schema_2 = schema_1.update(newProperties, 'me', now);
expect(schema_2.properties).toEqual(newProperties); expect(schema_2.properties).toEqual(newProperties);
@ -76,7 +76,7 @@ describe('SchemaDto', () => {
const now = DateTime.now(); const now = DateTime.now();
const schema_1 = new SchemaDetailsDto('1', 'name', properties, false, 'other', 'other', DateTime.now(), DateTime.now(), null, []); const schema_1 = new SchemaDetailsDto('1', 'name', properties, false, 'other', 'other', DateTime.now(), DateTime.now(), new Version('1'), []);
const schema_2 = schema_1.configureScripts(newScripts, 'me', now); const schema_2 = schema_1.configureScripts(newScripts, 'me', now);
expect(schema_2.scriptQuery).toEqual('<script-query>'); expect(schema_2.scriptQuery).toEqual('<script-query>');
@ -91,12 +91,12 @@ describe('SchemaDto', () => {
}); });
describe('SchemaDetailsDto', () => { describe('SchemaDetailsDto', () => {
const properties = new SchemaPropertiesDto('Name', null); const properties = new SchemaPropertiesDto('Name');
it('should update isPublished property and user info when publishing', () => { it('should update isPublished property and user info when publishing', () => {
const now = DateTime.now(); const now = DateTime.now();
const schema_1 = new SchemaDetailsDto('1', 'name', properties, false, 'other', 'other', DateTime.now(), DateTime.now(), null, []); const schema_1 = new SchemaDetailsDto('1', 'name', properties, false, 'other', 'other', DateTime.now(), DateTime.now(), new Version('1'), []);
const schema_2 = schema_1.publish('me', now); const schema_2 = schema_1.publish('me', now);
expect(schema_2.isPublished).toBeTruthy(); expect(schema_2.isPublished).toBeTruthy();
@ -107,7 +107,7 @@ describe('SchemaDetailsDto', () => {
it('should update isPublished property and user info when unpublishing', () => { it('should update isPublished property and user info when unpublishing', () => {
const now = DateTime.now(); const now = DateTime.now();
const schema_1 = new SchemaDetailsDto('1', 'name', properties, true, 'other', 'other', DateTime.now(), DateTime.now(), null, []); const schema_1 = new SchemaDetailsDto('1', 'name', properties, true, 'other', 'other', DateTime.now(), DateTime.now(), new Version('1'), []);
const schema_2 = schema_1.unpublish('me', now); const schema_2 = schema_1.unpublish('me', now);
expect(schema_2.isPublished).toBeFalsy(); expect(schema_2.isPublished).toBeFalsy();
@ -116,11 +116,11 @@ describe('SchemaDetailsDto', () => {
}); });
it('should update properties property and user info when updating', () => { it('should update properties property and user info when updating', () => {
const newProperties = new SchemaPropertiesDto('New Name', null); const newProperties = new SchemaPropertiesDto('New Name');
const now = DateTime.now(); const now = DateTime.now();
const schema_1 = new SchemaDetailsDto('1', 'name', properties, false, 'other', 'other', DateTime.now(), DateTime.now(), null, []); const schema_1 = new SchemaDetailsDto('1', 'name', properties, false, 'other', 'other', DateTime.now(), DateTime.now(), new Version('1'), []);
const schema_2 = schema_1.update(newProperties, 'me', now); const schema_2 = schema_1.update(newProperties, 'me', now);
expect(schema_2.properties).toEqual(newProperties); expect(schema_2.properties).toEqual(newProperties);
@ -134,7 +134,7 @@ describe('SchemaDetailsDto', () => {
const now = DateTime.now(); const now = DateTime.now();
const schema_1 = new SchemaDetailsDto('1', 'name', properties, false, 'other', 'other', DateTime.now(), DateTime.now(), null, [field1]); const schema_1 = new SchemaDetailsDto('1', 'name', properties, false, 'other', 'other', DateTime.now(), DateTime.now(), new Version('1'), [field1]);
const schema_2 = schema_1.addField(field2, 'me', now); const schema_2 = schema_1.addField(field2, 'me', now);
expect(schema_2.fields).toEqual([field1, field2]); expect(schema_2.fields).toEqual([field1, field2]);
@ -148,7 +148,7 @@ describe('SchemaDetailsDto', () => {
const now = DateTime.now(); const now = DateTime.now();
const schema_1 = new SchemaDetailsDto('1', 'name', properties, false, 'other', 'other', DateTime.now(), DateTime.now(), null, [field1, field2]); const schema_1 = new SchemaDetailsDto('1', 'name', properties, false, 'other', 'other', DateTime.now(), DateTime.now(), new Version('1'), [field1, field2]);
const schema_2 = schema_1.removeField(field1, 'me', now); const schema_2 = schema_1.removeField(field1, 'me', now);
expect(schema_2.fields).toEqual([field2]); expect(schema_2.fields).toEqual([field2]);
@ -162,7 +162,7 @@ describe('SchemaDetailsDto', () => {
const now = DateTime.now(); const now = DateTime.now();
const schema_1 = new SchemaDetailsDto('1', 'name', properties, false, 'other', 'other', DateTime.now(), DateTime.now(), null, [field1, field2]); const schema_1 = new SchemaDetailsDto('1', 'name', properties, false, 'other', 'other', DateTime.now(), DateTime.now(), new Version('1'), [field1, field2]);
const schema_2 = schema_1.replaceFields([field2, field1], 'me', now); const schema_2 = schema_1.replaceFields([field2, field1], 'me', now);
expect(schema_2.fields).toEqual([field2, field1]); expect(schema_2.fields).toEqual([field2, field1]);
@ -177,7 +177,7 @@ describe('SchemaDetailsDto', () => {
const now = DateTime.now(); const now = DateTime.now();
const schema_1 = new SchemaDetailsDto('1', 'name', properties, false, 'other', 'other', DateTime.now(), DateTime.now(), null, [field1_0, field2_1]); const schema_1 = new SchemaDetailsDto('1', 'name', properties, false, 'other', 'other', DateTime.now(), DateTime.now(), new Version('1'), [field1_0, field2_1]);
const schema_2 = schema_1.updateField(field2_2, 'me', now); const schema_2 = schema_1.updateField(field2_2, 'me', now);
expect(schema_2.fields).toEqual([field1_0, field2_2]); expect(schema_2.fields).toEqual([field1_0, field2_2]);
@ -461,7 +461,7 @@ describe('SchemasService', () => {
req.flush({ id: '1' }); req.flush({ id: '1' });
expect(schema).toEqual( expect(schema).toEqual(
new SchemaDetailsDto('1', dto.name, new SchemaPropertiesDto(null, null), false, user, user, now, now, version, [])); new SchemaDetailsDto('1', dto.name, new SchemaPropertiesDto(), false, user, user, now, now, version, []));
})); }));
it('should make post request to add field', it('should make post request to add field',

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

@ -643,8 +643,8 @@ export class JsonFieldPropertiesDto extends FieldPropertiesDto {
export class SchemaPropertiesDto { export class SchemaPropertiesDto {
constructor( constructor(
public readonly label: string, public readonly label?: string,
public readonly hints: string public readonly hints?: string
) { ) {
} }
} }
@ -781,7 +781,7 @@ export class SchemasService {
.pretifyError('Failed to load schema. Please reload.'); .pretifyError('Failed to load schema. Please reload.');
} }
public postSchema(appName: string, dto: CreateSchemaDto, user: string, now?: DateTime, version?: Version): Observable<SchemaDetailsDto> { public postSchema(appName: string, dto: CreateSchemaDto, user: string, now: DateTime, version: Version): Observable<SchemaDetailsDto> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas`);
return HTTP.postVersioned(this.http, url, dto, version) return HTTP.postVersioned(this.http, url, dto, version)
@ -791,13 +791,13 @@ export class SchemasService {
return new SchemaDetailsDto( return new SchemaDetailsDto(
response.id, response.id,
dto.name, dto.name,
dto.properties || new SchemaPropertiesDto(null, null), dto.properties || new SchemaPropertiesDto(),
false, false,
user, user,
user, user,
now, now,
now, now,
version, version || new Version('0'),
dto.fields || [], dto.fields || [],
response.scriptQuery, response.scriptQuery,
response.scriptCreate, response.scriptCreate,
@ -813,7 +813,7 @@ export class SchemasService {
.pretifyError('Failed to create schema. Please reload.'); .pretifyError('Failed to create schema. Please reload.');
} }
public postField(appName: string, schemaName: string, dto: AddFieldDto, version?: Version): Observable<FieldDto> { public postField(appName: string, schemaName: string, dto: AddFieldDto, version: Version): Observable<FieldDto> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/fields`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/fields`);
return HTTP.postVersioned(this.http, url, dto, version) return HTTP.postVersioned(this.http, url, dto, version)
@ -830,7 +830,7 @@ export class SchemasService {
.pretifyError('Failed to add field. Please reload.'); .pretifyError('Failed to add field. Please reload.');
} }
public deleteSchema(appName: string, schemaName: string, version?: Version): Observable<any> { public deleteSchema(appName: string, schemaName: string, version: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}`);
return HTTP.deleteVersioned(this.http, url, version) return HTTP.deleteVersioned(this.http, url, version)
@ -840,84 +840,84 @@ export class SchemasService {
.pretifyError('Failed to delete schema. Please reload.'); .pretifyError('Failed to delete schema. Please reload.');
} }
public putSchemaScripts(appName: string, schemaName: string, dto: UpdateSchemaScriptsDto, version?: Version): Observable<any> { public putSchemaScripts(appName: string, schemaName: string, dto: UpdateSchemaScriptsDto, version: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/scripts`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/scripts`);
return HTTP.putVersioned(this.http, url, dto, version) return HTTP.putVersioned(this.http, url, dto, version)
.pretifyError('Failed to update schema scripts. Please reload.'); .pretifyError('Failed to update schema scripts. Please reload.');
} }
public putSchema(appName: string, schemaName: string, dto: UpdateSchemaDto, version?: Version): Observable<any> { public putSchema(appName: string, schemaName: string, dto: UpdateSchemaDto, version: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}`);
return HTTP.putVersioned(this.http, url, dto, version) return HTTP.putVersioned(this.http, url, dto, version)
.pretifyError('Failed to update schema. Please reload.'); .pretifyError('Failed to update schema. Please reload.');
} }
public putFieldOrdering(appName: string, schemaName: string, dto: number[], version?: Version): Observable<any> { public putFieldOrdering(appName: string, schemaName: string, dto: number[], version: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/fields/ordering`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/fields/ordering`);
return HTTP.putVersioned(this.http, url, { fieldIds: dto }, version) return HTTP.putVersioned(this.http, url, { fieldIds: dto }, version)
.pretifyError('Failed to reorder fields. Please reload.'); .pretifyError('Failed to reorder fields. Please reload.');
} }
public publishSchema(appName: string, schemaName: string, version?: Version): Observable<any> { public publishSchema(appName: string, schemaName: string, version: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/publish`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/publish`);
return HTTP.putVersioned(this.http, url, {}, version) return HTTP.putVersioned(this.http, url, {}, version)
.pretifyError('Failed to publish schema. Please reload.'); .pretifyError('Failed to publish schema. Please reload.');
} }
public unpublishSchema(appName: string, schemaName: string, version?: Version): Observable<any> { public unpublishSchema(appName: string, schemaName: string, version: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/unpublish`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/unpublish`);
return HTTP.putVersioned(this.http, url, {}, version) return HTTP.putVersioned(this.http, url, {}, version)
.pretifyError('Failed to unpublish schema. Please reload.'); .pretifyError('Failed to unpublish schema. Please reload.');
} }
public putField(appName: string, schemaName: string, fieldId: number, dto: UpdateFieldDto, version?: Version): Observable<any> { public putField(appName: string, schemaName: string, fieldId: number, dto: UpdateFieldDto, version: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/fields/${fieldId}`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/fields/${fieldId}`);
return HTTP.putVersioned(this.http, url, dto, version) return HTTP.putVersioned(this.http, url, dto, version)
.pretifyError('Failed to update field. Please reload.'); .pretifyError('Failed to update field. Please reload.');
} }
public enableField(appName: string, schemaName: string, fieldId: number, version?: Version): Observable<any> { public enableField(appName: string, schemaName: string, fieldId: number, version: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/fields/${fieldId}/enable`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/fields/${fieldId}/enable`);
return HTTP.putVersioned(this.http, url, {}, version) return HTTP.putVersioned(this.http, url, {}, version)
.pretifyError('Failed to enable field. Please reload.'); .pretifyError('Failed to enable field. Please reload.');
} }
public disableField(appName: string, schemaName: string, fieldId: number, version?: Version): Observable<any> { public disableField(appName: string, schemaName: string, fieldId: number, version: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/fields/${fieldId}/disable`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/fields/${fieldId}/disable`);
return HTTP.putVersioned(this.http, url, {}, version) return HTTP.putVersioned(this.http, url, {}, version)
.pretifyError('Failed to disable field. Please reload.'); .pretifyError('Failed to disable field. Please reload.');
} }
public lockField(appName: string, schemaName: string, fieldId: number, version?: Version): Observable<any> { public lockField(appName: string, schemaName: string, fieldId: number, version: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/fields/${fieldId}/lock`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/fields/${fieldId}/lock`);
return HTTP.putVersioned(this.http, url, {}, version) return HTTP.putVersioned(this.http, url, {}, version)
.pretifyError('Failed to lock field. Please reload.'); .pretifyError('Failed to lock field. Please reload.');
} }
public showField(appName: string, schemaName: string, fieldId: number, version?: Version): Observable<any> { public showField(appName: string, schemaName: string, fieldId: number, version: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/fields/${fieldId}/show`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/fields/${fieldId}/show`);
return HTTP.putVersioned(this.http, url, {}, version) return HTTP.putVersioned(this.http, url, {}, version)
.pretifyError('Failed to show field. Please reload.'); .pretifyError('Failed to show field. Please reload.');
} }
public hideField(appName: string, schemaName: string, fieldId: number, version?: Version): Observable<any> { public hideField(appName: string, schemaName: string, fieldId: number, version: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/fields/${fieldId}/hide`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/fields/${fieldId}/hide`);
return HTTP.putVersioned(this.http, url, {}, version) return HTTP.putVersioned(this.http, url, {}, version)
.pretifyError('Failed to hide field. Please reload.'); .pretifyError('Failed to hide field. Please reload.');
} }
public deleteField(appName: string, schemaName: string, fieldId: number, version?: Version): Observable<any> { public deleteField(appName: string, schemaName: string, fieldId: number, version: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/fields/${fieldId}`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/fields/${fieldId}`);
return HTTP.deleteVersioned(this.http, url, version) return HTTP.deleteVersioned(this.http, url, version)

3
src/Squidex/app/shared/services/ui.service.spec.ts

@ -74,6 +74,7 @@ describe('UIService', () => {
req.error(new ErrorEvent('500')); req.error(new ErrorEvent('500'));
expect(settings.regexSuggestions).toEqual([]); expect(settings).toBeDefined();
expect(settings!.regexSuggestions).toEqual([]);
})); }));
}); });

4
src/Squidex/app/shared/services/webhooks.service.spec.ts

@ -67,14 +67,14 @@ describe('WebhooksService', () => {
let webhooks: WebhookDto[] | null = null; let webhooks: WebhookDto[] | null = null;
webhooksService.getWebhooks('my-app', version).subscribe(result => { webhooksService.getWebhooks('my-app').subscribe(result => {
webhooks = result; webhooks = result;
}); });
const req = httpMock.expectOne('http://service/p/api/apps/my-app/webhooks'); const req = httpMock.expectOne('http://service/p/api/apps/my-app/webhooks');
expect(req.request.method).toEqual('GET'); expect(req.request.method).toEqual('GET');
expect(req.request.headers.get('If-Match')).toBe(version.value); expect(req.request.headers.get('If-Match')).toBeNull();
req.flush([ req.flush([
{ {

10
src/Squidex/app/shared/services/webhooks.service.ts

@ -111,10 +111,10 @@ export class WebhooksService {
) { ) {
} }
public getWebhooks(appName: string, version?: Version): Observable<WebhookDto[]> { public getWebhooks(appName: string): Observable<WebhookDto[]> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/webhooks`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/webhooks`);
return HTTP.getVersioned(this.http, url, version) return HTTP.getVersioned(this.http, url)
.map(response => { .map(response => {
const items: any[] = response; const items: any[] = response;
@ -147,7 +147,7 @@ export class WebhooksService {
.pretifyError('Failed to load webhooks. Please reload.'); .pretifyError('Failed to load webhooks. Please reload.');
} }
public postWebhook(appName: string, dto: CreateWebhookDto, user: string, now?: DateTime, version?: Version): Observable<WebhookDto> { public postWebhook(appName: string, dto: CreateWebhookDto, user: string, now: DateTime, version: Version): Observable<WebhookDto> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/webhooks`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/webhooks`);
return HTTP.postVersioned(this.http, url, dto, version) return HTTP.postVersioned(this.http, url, dto, version)
@ -167,14 +167,14 @@ export class WebhooksService {
.pretifyError('Failed to create webhook. Please reload.'); .pretifyError('Failed to create webhook. Please reload.');
} }
public putWebhook(appName: string, id: string, dto: UpdateWebhookDto, version?: Version): Observable<any> { public putWebhook(appName: string, id: string, dto: UpdateWebhookDto, version: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/webhooks/${id}`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/webhooks/${id}`);
return HTTP.putVersioned(this.http, url, dto, version) return HTTP.putVersioned(this.http, url, dto, version)
.pretifyError('Failed to update webhook. Please reload.'); .pretifyError('Failed to update webhook. Please reload.');
} }
public deleteWebhook(appName: string, id: string, version?: Version): Observable<any> { public deleteWebhook(appName: string, id: string, version: Version): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/webhooks/${id}`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/webhooks/${id}`);
return HTTP.deleteVersioned(this.http, url, version) return HTTP.deleteVersioned(this.http, url, version)

6
src/Squidex/app/shell/pages/internal/profile-menu.component.ts

@ -49,10 +49,10 @@ export class ProfileMenuComponent implements OnDestroy, OnInit {
this.authenticationSubscription = this.authenticationSubscription =
this.authService.userChanges.filter(user => !!user) this.authService.userChanges.filter(user => !!user)
.subscribe(user => { .subscribe(user => {
this.profileId = user.id; this.profileId = user!.id;
this.profileDisplayName = user.displayName; this.profileDisplayName = user!.displayName;
this.isAdmin = user.isAdmin; this.isAdmin = user!.isAdmin;
}); });
} }

2
src/Squidex/tsconfig.json

@ -9,7 +9,7 @@
"noUnusedParameters": false, "noUnusedParameters": false,
"removeComments": false, "removeComments": false,
"sourceMap": true, "sourceMap": true,
"strictNullChecks": false, "strictNullChecks": true,
"suppressImplicitAnyIndexErrors": true, "suppressImplicitAnyIndexErrors": true,
"target": "es5", "target": "es5",
"paths": { "paths": {

Loading…
Cancel
Save