diff --git a/src/Squidex/app/features/administration/pages/users/user-page.component.ts b/src/Squidex/app/features/administration/pages/users/user-page.component.ts index 3be7e69a6..27d10c5ce 100644 --- a/src/Squidex/app/features/administration/pages/users/user-page.component.ts +++ b/src/Squidex/app/features/administration/pages/users/user-page.component.ts @@ -42,7 +42,7 @@ export class UserPageComponent implements OnDestroy, OnInit { this.selectedUserSubscription = this.usersState.selectedUser .subscribe(selectedUser => { - this.user = selectedUser; + this.user = selectedUser!; if (selectedUser) { this.userForm.load(selectedUser.user); diff --git a/src/Squidex/app/features/administration/pages/users/users-page.component.html b/src/Squidex/app/features/administration/pages/users/users-page.component.html index 4a8963e8f..3270262c4 100644 --- a/src/Squidex/app/features/administration/pages/users/users-page.component.html +++ b/src/Squidex/app/features/administration/pages/users/users-page.component.html @@ -81,7 +81,7 @@ diff --git a/src/Squidex/app/features/administration/state/event-consumers.state.ts b/src/Squidex/app/features/administration/state/event-consumers.state.ts index e98fd0cf8..ae16e1f21 100644 --- a/src/Squidex/app/features/administration/state/event-consumers.state.ts +++ b/src/Squidex/app/features/administration/state/event-consumers.state.ts @@ -21,7 +21,7 @@ import { EventConsumerDto, EventConsumersService } from './../services/event-con interface Snapshot { eventConsumers: ImmutableArray; - isLoaded?: false; + isLoaded?: boolean; } @Injectable() diff --git a/src/Squidex/app/features/administration/state/users.state.ts b/src/Squidex/app/features/administration/state/users.state.ts index 85266dcb9..05482ff9a 100644 --- a/src/Squidex/app/features/administration/state/users.state.ts +++ b/src/Squidex/app/features/administration/state/users.state.ts @@ -98,7 +98,7 @@ interface Snapshot { isLoaded?: boolean; - selectedUser?: SnapshotUser; + selectedUser?: SnapshotUser | null; } @Injectable() diff --git a/src/Squidex/app/features/content/pages/contents/contents-page.component.html b/src/Squidex/app/features/content/pages/contents/contents-page.component.html index dc771f257..7d7e8067d 100644 --- a/src/Squidex/app/features/content/pages/contents/contents-page.component.html +++ b/src/Squidex/app/features/content/pages/contents/contents-page.component.html @@ -120,7 +120,7 @@ diff --git a/src/Squidex/app/features/content/shared/contents-selector.component.html b/src/Squidex/app/features/content/shared/contents-selector.component.html index 263b77df6..d73ff6b2c 100644 --- a/src/Squidex/app/features/content/shared/contents-selector.component.html +++ b/src/Squidex/app/features/content/shared/contents-selector.component.html @@ -63,7 +63,7 @@ diff --git a/src/Squidex/app/features/rules/pages/events/rule-events-page.component.html b/src/Squidex/app/features/rules/pages/events/rule-events-page.component.html index c39cde203..b02c39f7b 100644 --- a/src/Squidex/app/features/rules/pages/events/rule-events-page.component.html +++ b/src/Squidex/app/features/rules/pages/events/rule-events-page.component.html @@ -90,7 +90,7 @@ - + diff --git a/src/Squidex/app/framework/angular/code.component.ts b/src/Squidex/app/framework/angular/code.component.ts index ec02060f5..7ae539d76 100644 --- a/src/Squidex/app/framework/angular/code.component.ts +++ b/src/Squidex/app/framework/angular/code.component.ts @@ -5,7 +5,9 @@ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ -import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component } from '@angular/core'; + +import { PureComponent } from '@app/framework/internal'; @Component({ selector: 'sqx-code', @@ -13,5 +15,8 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; templateUrl: './code.component.html', changeDetection: ChangeDetectionStrategy.OnPush }) -export class CodeComponent { +export class CodeComponent extends PureComponent { + constructor(changeDetector: ChangeDetectorRef) { + super(changeDetector); + } } \ No newline at end of file diff --git a/src/Squidex/app/framework/angular/forms/checkbox-group.component.html b/src/Squidex/app/framework/angular/forms/checkbox-group.component.html index 0f2a18bc1..de5b90311 100644 --- a/src/Squidex/app/framework/angular/forms/checkbox-group.component.html +++ b/src/Squidex/app/framework/angular/forms/checkbox-group.component.html @@ -1,9 +1,9 @@ - + [disabled]="snapshot.isDisabled"> - + \ No newline at end of file diff --git a/src/Squidex/app/framework/angular/forms/checkbox-group.component.ts b/src/Squidex/app/framework/angular/forms/checkbox-group.component.ts index 34c311056..90dc09607 100644 --- a/src/Squidex/app/framework/angular/forms/checkbox-group.component.ts +++ b/src/Squidex/app/framework/angular/forms/checkbox-group.component.ts @@ -8,14 +8,22 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, forwardRef, Input } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; -import { Types } from '@app/framework/internal'; - -import { MathHelper } from '../../utils/math-helper'; +import { + MathHelper, + StatefulComponent, + Types +} from '@app/framework/internal'; export const SQX_CHECKBOX_GROUP_CONTROL_VALUE_ACCESSOR: any = { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => CheckboxGroupComponent), multi: true }; +interface State { + checkedValues: string[]; + controlId: string; + isDisabled: boolean; +} + @Component({ selector: 'sqx-checkbox-group', styleUrls: ['./checkbox-group.component.scss'], @@ -23,33 +31,27 @@ export const SQX_CHECKBOX_GROUP_CONTROL_VALUE_ACCESSOR: any = { providers: [SQX_CHECKBOX_GROUP_CONTROL_VALUE_ACCESSOR], changeDetection: ChangeDetectionStrategy.OnPush }) -export class CheckboxGroupComponent implements ControlValueAccessor { +export class CheckboxGroupComponent extends StatefulComponent implements ControlValueAccessor { private callChange = (v: any) => { /* NOOP */ }; private callTouched = () => { /* NOOP */ }; - private checkedValues: string[] = []; @Input() public values: string[] = []; - public isDisabled = false; - - public control = MathHelper.guid(); - - constructor( - private readonly changeDetector: ChangeDetectorRef - ) { + constructor(changeDetector: ChangeDetectorRef) { + super(changeDetector, { + controlId: MathHelper.guid(), + checkedValues: [], + isDisabled: false + }); } public writeValue(obj: any) { - this.checkedValues = Types.isArrayOfString(obj) ? obj.filter(x => this.values.indexOf(x) >= 0) : []; - - this.changeDetector.markForCheck(); + this.next({ checkedValues: Types.isArrayOfString(obj) ? obj.filter(x => this.values.indexOf(x) >= 0) : [] }); } public setDisabledState(isDisabled: boolean): void { - this.isDisabled = isDisabled; - - this.changeDetector.markForCheck(); + this.next({ isDisabled }); } public registerOnChange(fn: any) { @@ -65,16 +67,20 @@ export class CheckboxGroupComponent implements ControlValueAccessor { } public check(isChecked: boolean, value: string) { + let checkedValues = this.snapshot.checkedValues; + if (isChecked) { - this.checkedValues = [value, ...this.checkedValues]; + checkedValues = [value, ...checkedValues]; } else { - this.checkedValues = this.checkedValues.filter(x => x !== value); + checkedValues = checkedValues.filter(x => x !== value); } - this.callChange(this.checkedValues); - } + this.next({ checkedValues }); + + this.callChange(checkedValues); + } public isChecked(value: string) { - return this.checkedValues.indexOf(value) >= 0; + return this.snapshot.checkedValues.indexOf(value) >= 0; } } diff --git a/src/Squidex/app/framework/angular/forms/code-editor.component.ts b/src/Squidex/app/framework/angular/forms/code-editor.component.ts index fd7cf17ec..7af142c50 100644 --- a/src/Squidex/app/framework/angular/forms/code-editor.component.ts +++ b/src/Squidex/app/framework/angular/forms/code-editor.component.ts @@ -5,12 +5,16 @@ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ -import { AfterViewInit, ChangeDetectionStrategy, Component, ElementRef, forwardRef, Input, ViewChild } from '@angular/core'; +import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, forwardRef, Input, ViewChild } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; import { Subject } from 'rxjs'; import { debounceTime } from 'rxjs/operators'; -import { ResourceLoaderService, Types } from '@app/framework/internal'; +import { + PureComponent, + ResourceLoaderService, + Types +} from '@app/framework/internal'; declare var ace: any; @@ -25,7 +29,7 @@ export const SQX_JSCRIPT_EDITOR_CONTROL_VALUE_ACCESSOR: any = { providers: [SQX_JSCRIPT_EDITOR_CONTROL_VALUE_ACCESSOR], changeDetection: ChangeDetectionStrategy.OnPush }) -export class CodeEditorComponent implements ControlValueAccessor, AfterViewInit { +export class CodeEditorComponent extends PureComponent implements ControlValueAccessor, AfterViewInit { private callChange = (v: any) => { /* NOOP */ }; private callTouched = () => { /* NOOP */ }; private valueChanged = new Subject(); @@ -40,8 +44,12 @@ export class CodeEditorComponent implements ControlValueAccessor, AfterViewInit public mode = 'ace/mode/javascript'; constructor( + changeDetector: ChangeDetectorRef, private readonly resourceLoader: ResourceLoaderService ) { + super(changeDetector); + + changeDetector.detach(); } public writeValue(obj: any) { diff --git a/src/Squidex/app/framework/angular/forms/control-errors.component.html b/src/Squidex/app/framework/angular/forms/control-errors.component.html index 5cab06ca2..3626cf842 100644 --- a/src/Squidex/app/framework/angular/forms/control-errors.component.html +++ b/src/Squidex/app/framework/angular/forms/control-errors.component.html @@ -1,6 +1,6 @@ -
+
- + {{message}}
diff --git a/src/Squidex/app/framework/angular/forms/control-errors.component.ts b/src/Squidex/app/framework/angular/forms/control-errors.component.ts index 673975dfc..1e2215f0b 100644 --- a/src/Squidex/app/framework/angular/forms/control-errors.component.ts +++ b/src/Squidex/app/framework/angular/forms/control-errors.component.ts @@ -7,12 +7,20 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Host, Input, OnChanges, OnDestroy, Optional } from '@angular/core'; import { AbstractControl, FormGroupDirective } from '@angular/forms'; -import { merge, Subscription } from 'rxjs'; +import { merge } from 'rxjs'; -import { fadeAnimation, Types } from '@app/framework/internal'; +import { + fadeAnimation, + StatefulComponent, + Types +} from '@app/framework/internal'; import { formatError } from './error-formatting'; +interface State { + errorMessages: string[]; +} + @Component({ selector: 'sqx-control-errors', styleUrls: ['./control-errors.component.scss'], @@ -22,10 +30,9 @@ import { formatError } from './error-formatting'; ], changeDetection: ChangeDetectionStrategy.OnPush }) -export class ControlErrorsComponent implements OnChanges, OnDestroy { +export class ControlErrorsComponent extends StatefulComponent implements OnChanges, OnDestroy { private displayFieldName: string; private control: AbstractControl; - private controlSubscription: Subscription | null = null; private originalMarkAsTouched: any; @Input() @@ -43,16 +50,20 @@ export class ControlErrorsComponent implements OnChanges, OnDestroy { @Input() public submitOnly = false; - public errorMessages: string[] = []; - - constructor( - @Optional() @Host() private readonly formGroupDirective: FormGroupDirective, - private readonly changeDetector: ChangeDetectorRef + constructor(changeDetector: ChangeDetectorRef, + @Optional() @Host() private readonly formGroupDirective: FormGroupDirective ) { + super(changeDetector, { + errorMessages: [] + }); } public ngOnDestroy() { - this.unsubscribe(); + super.ngOnDestroy(); + + if (this.control && this.originalMarkAsTouched) { + this.control['markAsTouched'] = this.originalMarkAsTouched; + } } public ngOnChanges() { @@ -75,16 +86,16 @@ export class ControlErrorsComponent implements OnChanges, OnDestroy { } if (this.control !== control) { - this.unsubscribe(); + this.ngOnDestroy(); this.control = control; if (control) { - this.controlSubscription = + this.observe( merge(control.valueChanges, control.statusChanges) .subscribe(() => { this.createMessages(); - }); + })); this.originalMarkAsTouched = this.control.markAsTouched; @@ -101,18 +112,8 @@ export class ControlErrorsComponent implements OnChanges, OnDestroy { this.createMessages(); } - private unsubscribe() { - if (this.controlSubscription) { - this.controlSubscription.unsubscribe(); - } - - if (this.control && this.originalMarkAsTouched) { - this.control['markAsTouched'] = this.originalMarkAsTouched; - } - } - private createMessages() { - const errors: string[] = []; + const errorMessages: string[] = []; if (this.control && this.control.invalid && ((this.control.touched && !this.submitOnly) || this.submitted) && this.control.errors) { for (let key in this.control.errors) { @@ -120,14 +121,12 @@ export class ControlErrorsComponent implements OnChanges, OnDestroy { const message = formatError(this.displayFieldName, key, this.control.errors[key], this.control.value, this.errors); if (message) { - errors.push(message); + errorMessages.push(message); } } } } - this.errorMessages = errors; - - this.changeDetector.markForCheck(); + this.next({ errorMessages }); } } \ No newline at end of file diff --git a/src/Squidex/app/framework/angular/forms/json-editor.component.ts b/src/Squidex/app/framework/angular/forms/json-editor.component.ts index 35f84d0b5..77e3adf1d 100644 --- a/src/Squidex/app/framework/angular/forms/json-editor.component.ts +++ b/src/Squidex/app/framework/angular/forms/json-editor.component.ts @@ -5,7 +5,7 @@ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ -import { AfterViewInit, ChangeDetectionStrategy, Component, ElementRef, forwardRef, ViewChild } from '@angular/core'; +import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, forwardRef, ViewChild } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; import { Subject } from 'rxjs'; import { debounceTime } from 'rxjs/operators'; @@ -37,9 +37,10 @@ export class JsonEditorComponent implements ControlValueAccessor, AfterViewInit @ViewChild('editor') public editor: ElementRef; - constructor( + constructor(changeDetector: ChangeDetectorRef, private readonly resourceLoader: ResourceLoaderService ) { + changeDetector.detach(); } public writeValue(obj: any) { diff --git a/src/Squidex/app/framework/angular/forms/progress-bar.component.ts b/src/Squidex/app/framework/angular/forms/progress-bar.component.ts index a5f300c0f..4a4d99649 100644 --- a/src/Squidex/app/framework/angular/forms/progress-bar.component.ts +++ b/src/Squidex/app/framework/angular/forms/progress-bar.component.ts @@ -5,7 +5,7 @@ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ -import { ChangeDetectionStrategy, Component, ElementRef, Input, OnChanges, OnInit, Renderer2, SimpleChanges } from '@angular/core'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, Input, OnChanges, OnInit, Renderer2, SimpleChanges } from '@angular/core'; import * as ProgressBar from 'progressbar.js'; @@ -38,10 +38,11 @@ export class ProgressBarComponent implements OnChanges, OnInit { @Input() public value = 0; - constructor( + constructor(changeDetector: ChangeDetectorRef, private readonly element: ElementRef, private readonly renderer: Renderer2 ) { + changeDetector.detach(); } public ngOnInit() { diff --git a/src/Squidex/app/framework/angular/forms/stars.component.html b/src/Squidex/app/framework/angular/forms/stars.component.html index 4f325a931..4cf2ca64f 100644 --- a/src/Squidex/app/framework/angular/forms/stars.component.html +++ b/src/Squidex/app/framework/angular/forms/stars.component.html @@ -4,8 +4,8 @@
- - + + diff --git a/src/Squidex/app/framework/angular/forms/stars.component.ts b/src/Squidex/app/framework/angular/forms/stars.component.ts index 26f45ef20..3a9772f9b 100644 --- a/src/Squidex/app/framework/angular/forms/stars.component.ts +++ b/src/Squidex/app/framework/angular/forms/stars.component.ts @@ -8,12 +8,21 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, forwardRef, Input } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; -import { Types } from '@app/framework/internal'; +import { StatefulComponent, Types } from '@app/framework/internal'; export const SQX_STARS_CONTROL_VALUE_ACCESSOR: any = { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => StarsComponent), multi: true }; +interface State { + isDisabled: boolean; + + stars: number; + starsArray: number[]; + + value: number | null; +} + @Component({ selector: 'sqx-stars', styleUrls: ['./stars.component.scss'], @@ -21,7 +30,7 @@ export const SQX_STARS_CONTROL_VALUE_ACCESSOR: any = { providers: [SQX_STARS_CONTROL_VALUE_ACCESSOR], changeDetection: ChangeDetectionStrategy.OnPush }) -export class StarsComponent implements ControlValueAccessor { +export class StarsComponent extends StatefulComponent implements ControlValueAccessor { private callChange = (v: any) => { /* NOOP */ }; private callTouched = () => { /* NOOP */ }; private maximumStarsValue = 5; @@ -33,11 +42,13 @@ export class StarsComponent implements ControlValueAccessor { if (this.maximumStarsValue !== maxStars) { this.maximumStarsValue = value; - this.starsArray = []; + const starsArray = []; for (let i = 1; i <= value; i++) { - this.starsArray.push(i); + starsArray.push(i); } + + this.next({ starsArray }); } } @@ -45,28 +56,23 @@ export class StarsComponent implements ControlValueAccessor { return this.maximumStarsValue; } - public isDisabled = false; - - public stars: number; - public starsArray: number[] = [1, 2, 3, 4, 5]; - - public value: number | null = 1; - - constructor( - private readonly changeDetector: ChangeDetectorRef - ) { + constructor(changeDetector: ChangeDetectorRef) { + super(changeDetector, { + isDisabled: false, + stars: -1, + starsArray: [1, 2, 3, 4, 5], + value: 1 + }); } public writeValue(obj: any) { - this.value = this.stars = Types.isNumber(obj) ? obj : 0; + const value = Types.isNumber(obj) ? obj : 0; - this.changeDetector.markForCheck(); + this.next({ stars: value, value }); } public setDisabledState(isDisabled: boolean): void { - this.isDisabled = isDisabled; - - this.changeDetector.markForCheck(); + this.next({ isDisabled }); } public registerOnChange(fn: any) { @@ -77,32 +83,31 @@ export class StarsComponent implements ControlValueAccessor { this.callTouched = fn; } - public setPreview(value: number) { - if (this.isDisabled) { + public setPreview(stars: number) { + if (this.snapshot.isDisabled) { return; } - this.stars = value; + this.next({ stars }); } public stopPreview() { - if (this.isDisabled) { + if (this.snapshot.isDisabled) { return; } - this.stars = this.value || 0; + this.next(s => { s.stars = s.value || 0; }); } public reset() { - if (this.isDisabled) { + if (this.snapshot.isDisabled) { return false; } - if (this.value) { - this.value = null; - this.stars = 0; + if (this.snapshot.value) { + this.next({ stars: -1, value: null }); - this.callChange(this.value); + this.callChange(null); this.callTouched(); } @@ -110,14 +115,14 @@ export class StarsComponent implements ControlValueAccessor { } public setValue(value: number) { - if (this.isDisabled) { + if (this.snapshot.isDisabled) { return false; } - if (this.value !== value) { - this.value = this.stars = value; + if (this.snapshot.value !== value) { + this.next({ stars: value, value }); - this.callChange(this.value); + this.callChange(value); this.callTouched(); } diff --git a/src/Squidex/app/framework/angular/forms/toggle.component.html b/src/Squidex/app/framework/angular/forms/toggle.component.html index 8c774acff..ecdde36e5 100644 --- a/src/Squidex/app/framework/angular/forms/toggle.component.html +++ b/src/Squidex/app/framework/angular/forms/toggle.component.html @@ -1,6 +1,6 @@
+ [class.disabled]="snapshot.isDisabled" + [class.checked]="snapshot.isChecked === true" + [class.unchecked]="snapshot.isChecked === false">
\ No newline at end of file diff --git a/src/Squidex/app/framework/angular/forms/toggle.component.ts b/src/Squidex/app/framework/angular/forms/toggle.component.ts index 1524d908b..94052ac3c 100644 --- a/src/Squidex/app/framework/angular/forms/toggle.component.ts +++ b/src/Squidex/app/framework/angular/forms/toggle.component.ts @@ -5,47 +5,48 @@ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ -import { ChangeDetectionStrategy, ChangeDetectorRef, Component, forwardRef, Input } from '@angular/core'; +import { ChangeDetectorRef, Component, forwardRef, Input } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; import { Types } from '@app/framework/internal'; +import { StatefulComponent } from '../stateful.component'; + export const SQX_TOGGLE_CONTROL_VALUE_ACCESSOR: any = { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => ToggleComponent), multi: true }; +interface State { + isDisabled: boolean; + isChecked: boolean | null; +} + @Component({ selector: 'sqx-toggle', styleUrls: ['./toggle.component.scss'], templateUrl: './toggle.component.html', - providers: [SQX_TOGGLE_CONTROL_VALUE_ACCESSOR], - changeDetection: ChangeDetectionStrategy.OnPush + providers: [SQX_TOGGLE_CONTROL_VALUE_ACCESSOR] }) -export class ToggleComponent implements ControlValueAccessor { +export class ToggleComponent extends StatefulComponent implements ControlValueAccessor { private callChange = (v: any) => { /* NOOP */ }; private callTouched = () => { /* NOOP */ }; @Input() public threeStates = false; - public isChecked: boolean | null = null; - public isDisabled = false; - - constructor( - private readonly changeDetector: ChangeDetectorRef - ) { + constructor(changeDetector: ChangeDetectorRef) { + super(changeDetector, { + isChecked: null, + isDisabled: false + }); } public writeValue(obj: any) { - this.isChecked = Types.isBoolean(obj) ? obj : null; - - this.changeDetector.markForCheck(); + this.next({ isChecked: Types.isBoolean(obj) ? obj : null }); } public setDisabledState(isDisabled: boolean): void { - this.isDisabled = isDisabled; - - this.changeDetector.markForCheck(); + this.next({ isDisabled }); } public registerOnChange(fn: any) { @@ -57,23 +58,27 @@ export class ToggleComponent implements ControlValueAccessor { } public changeState(event: MouseEvent) { - if (this.isDisabled) { + let { isDisabled, isChecked } = this.snapshot; + + if (isDisabled) { return; } if (this.threeStates && (event.ctrlKey || event.shiftKey)) { - if (this.isChecked) { - this.isChecked = null; - } else if (this.isChecked === null) { - this.isChecked = false; + if (isChecked) { + isChecked = null; + } else if (isChecked === null) { + isChecked = false; } else { - this.isChecked = true; + isChecked = true; } } else { - this.isChecked = !(this.isChecked === true); + isChecked = !(isChecked === true); } - this.callChange(this.isChecked); + this.next({ isChecked }); + + this.callChange(isChecked); this.callTouched(); } } \ No newline at end of file diff --git a/src/Squidex/app/framework/angular/image-source.directive.ts b/src/Squidex/app/framework/angular/image-source.directive.ts index c9406b524..cdfb16fd7 100644 --- a/src/Squidex/app/framework/angular/image-source.directive.ts +++ b/src/Squidex/app/framework/angular/image-source.directive.ts @@ -7,7 +7,7 @@ import { AfterViewInit, Directive, ElementRef, HostListener, Input, OnChanges, OnDestroy, OnInit, Renderer2 } from '@angular/core'; -import { MathHelper } from './../utils/math-helper'; +import { MathHelper } from '@app/framework/internal'; const LAYOUT_CACHE: { [key: string]: { width: number, height: number } } = {}; diff --git a/src/Squidex/app/framework/angular/modals/dialog-renderer.component.html b/src/Squidex/app/framework/angular/modals/dialog-renderer.component.html index f39ac71d5..0dcb911a0 100644 --- a/src/Squidex/app/framework/angular/modals/dialog-renderer.component.html +++ b/src/Squidex/app/framework/angular/modals/dialog-renderer.component.html @@ -2,11 +2,11 @@ - {{dialogRequest?.title}} + {{snapshot.dialogRequest?.title}} - {{dialogRequest?.text}} + {{snapshot.dialogRequest?.text}} @@ -16,7 +16,7 @@
-
+
diff --git a/src/Squidex/app/framework/angular/modals/dialog-renderer.component.ts b/src/Squidex/app/framework/angular/modals/dialog-renderer.component.ts index 17c184f0c..335a19da0 100644 --- a/src/Squidex/app/framework/angular/modals/dialog-renderer.component.ts +++ b/src/Squidex/app/framework/angular/modals/dialog-renderer.component.ts @@ -5,8 +5,8 @@ * Copyright (c) Sebastian Stehle. All rights r vbeserved */ -import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy, OnInit } from '@angular/core'; -import { Subscription } from 'rxjs'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit } from '@angular/core'; +import { timer } from 'rxjs'; import { DialogModel, @@ -16,6 +16,14 @@ import { Notification } from '@app/framework/internal'; +import { StatefulComponent } from '../stateful.component'; + +interface State { + dialogRequest?: DialogRequest | null; + + notifications: Notification[]; +} + @Component({ selector: 'sqx-dialog-renderer', styleUrls: ['./dialog-renderer.component.scss'], @@ -25,89 +33,74 @@ import { ], changeDetection: ChangeDetectionStrategy.OnPush }) -export class DialogRendererComponent implements OnDestroy, OnInit { - private dialogSubscription: Subscription; - private dialogsSubscription: Subscription; - private notificationsSubscription: Subscription; - - public dialogView = new DialogModel(); - public dialogRequest: DialogRequest | null = null; - - public notifications: Notification[] = []; - +export class DialogRendererComponent extends StatefulComponent implements OnInit { @Input() public position = 'bottomright'; - constructor( - private readonly changeDetector: ChangeDetectorRef, + public dialogView = new DialogModel(); + + constructor(changeDetector: ChangeDetectorRef, private readonly dialogs: DialogService ) { - } - - public ngOnDestroy() { - this.notificationsSubscription.unsubscribe(); - this.dialogSubscription.unsubscribe(); - this.dialogsSubscription.unsubscribe(); + super(changeDetector, { notifications: [] }); } public ngOnInit() { - this.dialogSubscription = + this.observe( this.dialogView.isOpen.subscribe(isOpen => { if (!isOpen) { - this.cancel(); - - this.changeDetector.detectChanges(); + this.finishRequest(false); } - }); + })); - this.notificationsSubscription = + this.observe( this.dialogs.notifications.subscribe(notification => { - this.notifications.push(notification); + this.next(state => { + state.notifications = [...state.notifications, notification]; + }); if (notification.displayTime > 0) { - setTimeout(() => { + this.observe(timer(notification.displayTime).subscribe(() => { this.close(notification); - }, notification.displayTime); + })); } + })); - this.changeDetector.detectChanges(); - }); - - this.dialogsSubscription = + this.observe( this.dialogs.dialogs .subscribe(request => { this.cancel(); - this.dialogRequest = request; - this.dialogView.show(); - - this.changeDetector.detectChanges(); - }); + this.next(state => { + state.dialogRequest = request; + }); + })); } public cancel() { - if (this.dialogRequest) { - this.dialogRequest.complete(false); - this.dialogRequest = null; - this.dialogView.hide(); - } + this.finishRequest(false); + + this.dialogView.hide(); } public confirm() { - if (this.dialogRequest) { - this.dialogRequest.complete(true); - this.dialogRequest = null; - this.dialogView.hide(); - } - } + this.finishRequest(true); - public close(notification: Notification) { - const index = this.notifications.indexOf(notification); + this.dialogView.hide(); + } - if (index >= 0) { - this.notifications.splice(index, 1); + private finishRequest(value: boolean) { + this.next(state => { + if (state.dialogRequest) { + state.dialogRequest.complete(value); + state.dialogRequest = null; + } + }); + } - this.changeDetector.detectChanges(); - } + public close(notification: Notification) { + this.next(state => { + state.notifications = state.notifications.filter(n => notification !== n); + }); } } \ No newline at end of file diff --git a/src/Squidex/app/framework/angular/modals/modal-dialog.component.ts b/src/Squidex/app/framework/angular/modals/modal-dialog.component.ts index 20c685ff0..1282a4cb5 100644 --- a/src/Squidex/app/framework/angular/modals/modal-dialog.component.ts +++ b/src/Squidex/app/framework/angular/modals/modal-dialog.component.ts @@ -7,7 +7,7 @@ import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, EventEmitter, Input, Output, ViewChild } from '@angular/core'; -import { fadeAnimation } from './../animations'; +import { fadeAnimation } from '@app/framework/internal'; @Component({ selector: 'sqx-modal-dialog', @@ -16,7 +16,7 @@ import { fadeAnimation } from './../animations'; animations: [ fadeAnimation ], - changeDetection: ChangeDetectionStrategy.Default + changeDetection: ChangeDetectionStrategy.OnPush }) export class ModalDialogComponent implements AfterViewInit { @Input() diff --git a/src/Squidex/app/framework/angular/modals/onboarding-tooltip.component.ts b/src/Squidex/app/framework/angular/modals/onboarding-tooltip.component.ts index 14a8fc598..70b6c1cb3 100644 --- a/src/Squidex/app/framework/angular/modals/onboarding-tooltip.component.ts +++ b/src/Squidex/app/framework/angular/modals/onboarding-tooltip.component.ts @@ -6,11 +6,13 @@ */ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy, OnInit, Renderer2 } from '@angular/core'; +import { timer } from 'rxjs'; import { fadeAnimation, ModalModel, OnboardingService, + PureComponent, Types } from '@app/framework/internal'; @@ -23,11 +25,7 @@ import { ], changeDetection: ChangeDetectionStrategy.OnPush }) -export class OnboardingTooltipComponent implements OnDestroy, OnInit { - private showTimer: any; - private closeTimer: any; - private forMouseDownListener: Function | null; - +export class OnboardingTooltipComponent extends PureComponent implements OnDestroy, OnInit { public tooltipModal = new ModalModel(); @Input() @@ -42,56 +40,50 @@ export class OnboardingTooltipComponent implements OnDestroy, OnInit { @Input() public position = 'left'; - constructor( - private readonly changeDetector: ChangeDetectorRef, + constructor(changeDetector: ChangeDetectorRef, private readonly onboardingService: OnboardingService, private readonly renderer: Renderer2 ) { + super(changeDetector); } public ngOnDestroy() { - clearTimeout(this.showTimer); - clearTimeout(this.closeTimer); + super.ngOnDestroy(); this.tooltipModal.hide(); - - if (this.forMouseDownListener) { - this.forMouseDownListener(); - this.forMouseDownListener = null; - } } public ngOnInit() { if (this.for && this.helpId && Types.isFunction(this.for.addEventListener)) { - this.showTimer = setTimeout(() => { - if (this.onboardingService.shouldShow(this.helpId)) { - const forRect = this.for.getBoundingClientRect(); - - const x = forRect.left + 0.5 * forRect.width; - const y = forRect.top + 0.5 * forRect.height; + this.observe( + timer(this.after).subscribe(() => { + if (this.onboardingService.shouldShow(this.helpId)) { + const forRect = this.for.getBoundingClientRect(); - const fromPoint = document.elementFromPoint(x, y); + const x = forRect.left + 0.5 * forRect.width; + const y = forRect.top + 0.5 * forRect.height; - if (this.isSameOrParent(fromPoint)) { - this.tooltipModal.show(); + const fromPoint = document.elementFromPoint(x, y); - this.changeDetector.markForCheck(); + if (this.isSameOrParent(fromPoint)) { + this.tooltipModal.show(); - this.closeTimer = setTimeout(() => { - this.hideThis(); - }, 10000); + this.observe( + timer(10000).subscribe(() => { + this.hideThis(); + })); - this.onboardingService.disable(this.helpId); + this.onboardingService.disable(this.helpId); + } } - } - }, this.after); + })); - this.forMouseDownListener = + this.observe( this.renderer.listen(this.for, 'mousedown', () => { this.onboardingService.disable(this.helpId); this.hideThis(); - }); + })); } } diff --git a/src/Squidex/app/framework/angular/modals/root-view.component.ts b/src/Squidex/app/framework/angular/modals/root-view.component.ts index 2f3ec887c..2bd9862dd 100644 --- a/src/Squidex/app/framework/angular/modals/root-view.component.ts +++ b/src/Squidex/app/framework/angular/modals/root-view.component.ts @@ -5,7 +5,9 @@ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ -import { ChangeDetectionStrategy, Component, ViewChild, ViewContainerRef } from '@angular/core'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ViewChild, ViewContainerRef } from '@angular/core'; + +import { PureComponent } from '@app/framework/internal'; @Component({ selector: 'sqx-root-view', @@ -13,7 +15,11 @@ import { ChangeDetectionStrategy, Component, ViewChild, ViewContainerRef } from templateUrl: './root-view.component.html', changeDetection: ChangeDetectionStrategy.OnPush }) -export class RootViewComponent { +export class RootViewComponent extends PureComponent { @ViewChild('element', { read: ViewContainerRef }) public viewContainer: ViewContainerRef; + + constructor(changeDetector: ChangeDetectorRef) { + super(changeDetector); + } } \ No newline at end of file diff --git a/src/Squidex/app/framework/angular/modals/tooltip.component.ts b/src/Squidex/app/framework/angular/modals/tooltip.component.ts index e58c03243..8a0e558a8 100644 --- a/src/Squidex/app/framework/angular/modals/tooltip.component.ts +++ b/src/Squidex/app/framework/angular/modals/tooltip.component.ts @@ -7,9 +7,11 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy, OnInit, Renderer2 } from '@angular/core'; -import { ModalModel } from './../../utils/modal-view'; - -import { fadeAnimation } from './../animations'; +import { + fadeAnimation, + ModalModel, + PureComponent +} from '@app/framework/internal'; @Component({ selector: 'sqx-tooltip', @@ -20,10 +22,7 @@ import { fadeAnimation } from './../animations'; ], changeDetection: ChangeDetectionStrategy.OnPush }) -export class TooltipComponent implements OnDestroy, OnInit { - private targetMouseEnterListener: any; - private targetMouseLeaveListener: any; - +export class TooltipComponent extends PureComponent implements OnDestroy, OnInit { @Input() public target: any; @@ -32,35 +31,23 @@ export class TooltipComponent implements OnDestroy, OnInit { public modal = new ModalModel(); - constructor( - private readonly changeDetector: ChangeDetectorRef, + constructor(changeDetector: ChangeDetectorRef, private readonly renderer: Renderer2 ) { - } - - public ngOnDestroy() { - if (this.targetMouseEnterListener) { - this.targetMouseEnterListener(); - } - - if (this.targetMouseLeaveListener) { - this.targetMouseLeaveListener(); - } + super(changeDetector); } public ngOnInit() { if (this.target) { - this.targetMouseEnterListener = + this.observe( this.renderer.listen(this.target, 'mouseenter', () => { this.modal.show(); + })); - this.changeDetector.markForCheck(); - }); - - this.targetMouseLeaveListener = + this.observe( this.renderer.listen(this.target, 'mouseleave', () => { this.modal.hide(); - }); + })); } } } \ No newline at end of file diff --git a/src/Squidex/app/framework/angular/pager.component.html b/src/Squidex/app/framework/angular/pager.component.html index cd328ec9d..47899d0c8 100644 --- a/src/Squidex/app/framework/angular/pager.component.html +++ b/src/Squidex/app/framework/angular/pager.component.html @@ -2,10 +2,10 @@ diff --git a/src/Squidex/app/framework/angular/pager.component.ts b/src/Squidex/app/framework/angular/pager.component.ts index ce88403d7..dce6f8f8b 100644 --- a/src/Squidex/app/framework/angular/pager.component.ts +++ b/src/Squidex/app/framework/angular/pager.component.ts @@ -5,9 +5,9 @@ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ -import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, Output } from '@angular/core'; -import { Pager } from './../internal'; +import { Pager, PureComponent } from '@app/framework/internal'; @Component({ selector: 'sqx-pager', @@ -15,16 +15,20 @@ import { Pager } from './../internal'; templateUrl: './pager.component.html', changeDetection: ChangeDetectionStrategy.OnPush }) -export class PagerComponent { +export class PagerComponent extends PureComponent { + @Output() + public nextPage = new EventEmitter(); + + @Output() + public prevPage = new EventEmitter(); + @Input() public pager: Pager; @Input() public hideWhenButtonsDisabled = false; - @Output() - public next = new EventEmitter(); - - @Output() - public prev = new EventEmitter(); + constructor(changeDetector: ChangeDetectorRef) { + super(changeDetector); + } } \ No newline at end of file diff --git a/src/Squidex/app/framework/angular/panel.component.ts b/src/Squidex/app/framework/angular/panel.component.ts index 4556ed9b7..68bdb01d4 100644 --- a/src/Squidex/app/framework/angular/panel.component.ts +++ b/src/Squidex/app/framework/angular/panel.component.ts @@ -5,9 +5,9 @@ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ -import { AfterViewInit, ChangeDetectionStrategy, Component, ElementRef, Input, OnDestroy, OnInit, Renderer2, ViewChild } from '@angular/core'; +import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, Input, OnDestroy, OnInit, Renderer2, ViewChild } from '@angular/core'; -import { slideRightAnimation } from './animations'; +import { PureComponent, slideRightAnimation } from '@app/framework/internal'; import { PanelContainerDirective } from './panel-container.directive'; @@ -20,7 +20,7 @@ import { PanelContainerDirective } from './panel-container.directive'; ], changeDetection: ChangeDetectionStrategy.OnPush }) -export class PanelComponent implements AfterViewInit, OnDestroy, OnInit { +export class PanelComponent extends PureComponent implements AfterViewInit, OnDestroy, OnInit { private styleWidth: string; public renderWidth = 0; @@ -61,10 +61,11 @@ export class PanelComponent implements AfterViewInit, OnDestroy, OnInit { @ViewChild('panel') public panel: ElementRef; - constructor( + constructor(changeDetector: ChangeDetectorRef, private readonly container: PanelContainerDirective, private readonly renderer: Renderer2 ) { + super(changeDetector); } public ngOnDestroy() { diff --git a/src/Squidex/app/framework/angular/pipes/date-time.pipes.ts b/src/Squidex/app/framework/angular/pipes/date-time.pipes.ts index 145014c81..1da8095fc 100644 --- a/src/Squidex/app/framework/angular/pipes/date-time.pipes.ts +++ b/src/Squidex/app/framework/angular/pipes/date-time.pipes.ts @@ -7,8 +7,7 @@ import { Pipe, PipeTransform } from '@angular/core'; -import { DateTime } from './../../utils/date-time'; -import { Duration } from './../../utils/duration'; +import { DateTime, Duration } from '@app/framework/internal'; @Pipe({ name: 'sqxShortDate', diff --git a/src/Squidex/app/framework/angular/pipes/money.pipe.ts b/src/Squidex/app/framework/angular/pipes/money.pipe.ts index 8508ae21b..0c4cc4f71 100644 --- a/src/Squidex/app/framework/angular/pipes/money.pipe.ts +++ b/src/Squidex/app/framework/angular/pipes/money.pipe.ts @@ -7,7 +7,7 @@ import { Pipe, PipeTransform } from '@angular/core'; -import { CurrencyConfig, DecimalSeparatorConfig } from './../../configurations'; +import { CurrencyConfig, DecimalSeparatorConfig } from '@app/framework/internal'; @Pipe({ name: 'sqxMoney', diff --git a/src/Squidex/app/framework/angular/pipes/name.pipe.ts b/src/Squidex/app/framework/angular/pipes/name.pipe.ts index 49e12ec4c..c82448a55 100644 --- a/src/Squidex/app/framework/angular/pipes/name.pipe.ts +++ b/src/Squidex/app/framework/angular/pipes/name.pipe.ts @@ -7,7 +7,7 @@ import { Pipe, PipeTransform } from '@angular/core'; -import { StringHelper } from './../../utils/string-helper'; +import { StringHelper } from '@app/framework/internal'; @Pipe({ name: 'sqxDisplayName', diff --git a/src/Squidex/app/framework/angular/shortcut.component.spec.ts b/src/Squidex/app/framework/angular/shortcut.component.spec.ts index 146285756..ff725a20f 100644 --- a/src/Squidex/app/framework/angular/shortcut.component.spec.ts +++ b/src/Squidex/app/framework/angular/shortcut.component.spec.ts @@ -18,13 +18,13 @@ describe('ShortcutComponent', () => { }); it('should instantiate', () => { - const shortcutComponent = new ShortcutComponent(shortcutService, new NgZone({})); + const shortcutComponent = new ShortcutComponent({}, shortcutService, new NgZone({})); expect(shortcutComponent).toBeDefined(); }); it('should init without keys', () => { - const shortcutComponent = new ShortcutComponent(shortcutService, new NgZone({})); + const shortcutComponent = new ShortcutComponent({}, shortcutService, new NgZone({})); shortcutComponent.keys = null!; shortcutComponent.ngOnInit(); @@ -33,7 +33,7 @@ describe('ShortcutComponent', () => { }); it('should destroy without keys', () => { - const shortcutComponent = new ShortcutComponent(shortcutService, new NgZone({})); + const shortcutComponent = new ShortcutComponent({}, shortcutService, new NgZone({})); shortcutComponent.keys = null!; shortcutComponent.ngOnDestroy(); @@ -42,7 +42,7 @@ describe('ShortcutComponent', () => { }); it('should raise event when triggered', () => { - const shortcutComponent = new ShortcutComponent(shortcutService, new NgZone({})); + const shortcutComponent = new ShortcutComponent({}, shortcutService, new NgZone({})); let isTriggered = false; @@ -56,7 +56,7 @@ describe('ShortcutComponent', () => { }); it('should not raise event when triggered but disabled', () => { - const shortcutComponent = new ShortcutComponent(shortcutService, new NgZone({})); + const shortcutComponent = new ShortcutComponent({}, shortcutService, new NgZone({})); let isTriggered = false; @@ -71,7 +71,7 @@ describe('ShortcutComponent', () => { }); it('should not raise event when triggered but destroyed', () => { - const shortcutComponent = new ShortcutComponent(shortcutService, new NgZone({})); + const shortcutComponent = new ShortcutComponent({}, shortcutService, new NgZone({})); let isTriggered = false; diff --git a/src/Squidex/app/framework/angular/shortcut.component.ts b/src/Squidex/app/framework/angular/shortcut.component.ts index 26191b2eb..c264be77d 100644 --- a/src/Squidex/app/framework/angular/shortcut.component.ts +++ b/src/Squidex/app/framework/angular/shortcut.component.ts @@ -5,16 +5,17 @@ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ -import { ChangeDetectionStrategy, Component, EventEmitter, Input, NgZone, OnDestroy, OnInit, Output } from '@angular/core'; +import { ChangeDetectorRef, Component, EventEmitter, Input, NgZone, OnDestroy, OnInit, Output } from '@angular/core'; -import { ShortcutService } from './../services/shortcut.service'; +import { PureComponent, ShortcutService } from '@app/framework/internal'; @Component({ selector: 'sqx-shortcut', - template: '', - changeDetection: ChangeDetectionStrategy.OnPush + template: '' }) -export class ShortcutComponent implements OnDestroy, OnInit { +export class ShortcutComponent extends PureComponent implements OnDestroy, OnInit { + private lastKeys: string; + @Input() public keys: string; @@ -24,12 +25,14 @@ export class ShortcutComponent implements OnDestroy, OnInit { @Output() public trigger = new EventEmitter(); - private lastKeys: string; - constructor( + changeDetector: ChangeDetectorRef, private readonly shortcutService: ShortcutService, private readonly zone: NgZone ) { + super(changeDetector); + + changeDetector.detach(); } public ngOnDestroy() { @@ -42,10 +45,10 @@ export class ShortcutComponent implements OnDestroy, OnInit { this.lastKeys = this.keys; if (this.lastKeys) { - this.shortcutService.on(this.lastKeys, e => { + this.shortcutService.on(this.lastKeys, () => { if (!this.disabled) { this.zone.run(() => { - this.trigger.next(e); + this.trigger.next(); }); } diff --git a/src/Squidex/app/framework/angular/stateful.component.ts b/src/Squidex/app/framework/angular/stateful.component.ts new file mode 100644 index 000000000..330ecaa34 --- /dev/null +++ b/src/Squidex/app/framework/angular/stateful.component.ts @@ -0,0 +1,58 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. + */ + +import { ChangeDetectorRef, OnDestroy, OnInit } from '@angular/core'; +import { Subscription } from 'rxjs'; + +import { Types } from './../utils/types'; + +import { State } from '../state'; + +declare type UnsubscribeFunction = () => void; + +export abstract class StatefulComponent extends State implements OnDestroy, OnInit { + private subscriptions: (Subscription | UnsubscribeFunction)[] = []; + + constructor( + private readonly changeDetector: ChangeDetectorRef, + state: T + ) { + super(state); + } + + protected observe(subscription: Subscription | UnsubscribeFunction) { + if (subscription) { + this.subscriptions.push(subscription); + } + } + + public ngOnInit() { + this.changes.subscribe(() => { + this.changeDetector.detectChanges(); + }); + } + + public ngOnDestroy() { + try { + for (let subscription of this.subscriptions) { + if (Types.isFunction(subscription)) { + subscription(); + } else { + subscription.unsubscribe(); + } + } + } finally { + this.subscriptions = []; + } + } +} + +export abstract class PureComponent extends StatefulComponent { + constructor(changeDetector: ChangeDetectorRef) { + super(changeDetector, {}); + } +} \ No newline at end of file diff --git a/src/Squidex/app/framework/angular/title.component.ts b/src/Squidex/app/framework/angular/title.component.ts index 20161b09a..63510e7dc 100644 --- a/src/Squidex/app/framework/angular/title.component.ts +++ b/src/Squidex/app/framework/angular/title.component.ts @@ -7,7 +7,7 @@ import { ChangeDetectionStrategy, Component, Input, OnChanges } from '@angular/core'; -import { TitleService } from './../services/title.service'; +import { TitleService } from '@app/framework/internal'; @Component({ selector: 'sqx-title', diff --git a/src/Squidex/app/framework/angular/user-report.component.ts b/src/Squidex/app/framework/angular/user-report.component.ts index 0cb33edb7..ed197e543 100644 --- a/src/Squidex/app/framework/angular/user-report.component.ts +++ b/src/Squidex/app/framework/angular/user-report.component.ts @@ -5,36 +5,36 @@ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ -import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core'; +import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core'; +import { timer } from 'rxjs'; -import { UserReportConfig } from './../configurations'; -import { ResourceLoaderService } from './../services/resource-loader.service'; +import { + PureComponent, + ResourceLoaderService, + UserReportConfig +} from '@app/framework/internal'; @Component({ selector: 'sqx-user-report', - template: '', - changeDetection: ChangeDetectionStrategy.OnPush + template: '' }) -export class UserReportComponent implements OnDestroy, OnInit { - private loadingTimer: any; - - constructor(config: UserReportConfig, changeDetector: ChangeDetectorRef, +export class UserReportComponent extends PureComponent implements OnDestroy, OnInit { + constructor(changeDetector: ChangeDetectorRef, + private readonly config: UserReportConfig, private readonly resourceLoader: ResourceLoaderService ) { - changeDetector.detach(); - - window['_urq'] = window['_urq'] || []; - window['_urq'].push(['initSite', config.siteId]); - } + super(changeDetector); - public ngOnDestroy() { - clearTimeout(this.loadingTimer); + changeDetector.detach(); } public ngOnInit() { - this.loadingTimer = - setTimeout(() => { + window['_urq'] = window['_urq'] || []; + window['_urq'].push(['initSite', this.config.siteId]); + + this.observe( + timer(4000).subscribe(() => { this.resourceLoader.loadScript('https://cdn.userreport.com/userreport.js'); - }, 4000); + })); } } \ No newline at end of file diff --git a/src/Squidex/app/framework/internal.ts b/src/Squidex/app/framework/internal.ts index e738bbd3d..0f1556ebe 100644 --- a/src/Squidex/app/framework/internal.ts +++ b/src/Squidex/app/framework/internal.ts @@ -35,4 +35,6 @@ export * from './utils/string-helper'; export * from './utils/types'; export * from './utils/version'; +export * from './angular/stateful.component'; + export * from './configurations'; \ No newline at end of file diff --git a/src/Squidex/app/framework/state.ts b/src/Squidex/app/framework/state.ts index 03ed7e181..d7690801d 100644 --- a/src/Squidex/app/framework/state.ts +++ b/src/Squidex/app/framework/state.ts @@ -15,7 +15,7 @@ import { fullValue} from './angular/forms/forms-helper'; export interface FormState { submitted: boolean; - error?: string; + error?: string | null; } export class Form { @@ -136,11 +136,14 @@ export class State { this.next(this.initialState); } - public next(update: ((v: T) => T) | object) { + public next(update: ((v: T) => T | void) | Partial) { if (Types.isFunction(update)) { - this.state.next(update(this.state.value)); + const stateNew = { ...this.snapshot }; + const stateUpdated = update(stateNew); + + this.state.next(stateUpdated || stateNew); } else { - this.state.next(Object.assign({}, this.snapshot, update)); + this.state.next({ ...this.snapshot, ...update }); } } } \ No newline at end of file diff --git a/src/Squidex/app/shared/components/assets-list.component.html b/src/Squidex/app/shared/components/assets-list.component.html index d2f849a28..9133d6d70 100644 --- a/src/Squidex/app/shared/components/assets-list.component.html +++ b/src/Squidex/app/shared/components/assets-list.component.html @@ -35,4 +35,4 @@
- \ No newline at end of file + \ No newline at end of file diff --git a/src/Squidex/app/shared/state/apps.state.ts b/src/Squidex/app/shared/state/apps.state.ts index 1eff17ed3..ec4a13c2d 100644 --- a/src/Squidex/app/shared/state/apps.state.ts +++ b/src/Squidex/app/shared/state/apps.state.ts @@ -67,9 +67,9 @@ export class AppsState extends State { public load(): Observable { return this.appsService.getApps().pipe( - tap(dtos => { + tap((dto: AppDto[]) => { this.next(s => { - const apps = ImmutableArray.of(dtos); + const apps = ImmutableArray.of(dto); return { ...s, apps }; }); diff --git a/src/Squidex/app/shared/state/assets.state.ts b/src/Squidex/app/shared/state/assets.state.ts index 39f08819d..8563f4ee6 100644 --- a/src/Squidex/app/shared/state/assets.state.ts +++ b/src/Squidex/app/shared/state/assets.state.ts @@ -28,7 +28,7 @@ interface Snapshot { assetsPager: Pager; assetsQuery?: string; - isLoaded?: false; + isLoaded?: boolean; } @Injectable() diff --git a/src/Squidex/app/shared/state/contents.forms.ts b/src/Squidex/app/shared/state/contents.forms.ts index 02f96ce36..956d5e255 100644 --- a/src/Squidex/app/shared/state/contents.forms.ts +++ b/src/Squidex/app/shared/state/contents.forms.ts @@ -147,7 +147,7 @@ export class FieldValidatorsFactory implements FieldPropertiesVisitor 0) { diff --git a/src/Squidex/app/shared/state/schemas.state.ts b/src/Squidex/app/shared/state/schemas.state.ts index a4f7aa4de..0ae83627d 100644 --- a/src/Squidex/app/shared/state/schemas.state.ts +++ b/src/Squidex/app/shared/state/schemas.state.ts @@ -325,7 +325,7 @@ export class SchemasState extends State { private replaceSchema(schema: SchemaDto) { return this.next(s => { const schemas = s.schemas.replaceBy('id', schema).sortByStringAsc(x => x.displayName); - const selectedSchema = s.selectedSchema && s.selectedSchema.id === schema.id ? schema : s.selectedSchema; + const selectedSchema = Types.is(schema, SchemaDetailsDto) && s.selectedSchema && s.selectedSchema.id === schema.id ? schema : s.selectedSchema; const categories = buildCategories(s.categories, schemas);