Browse Source

Started with stateful component.

pull/345/head
Sebastian Stehle 7 years ago
parent
commit
5f154e1485
  1. 2
      src/Squidex/app/features/administration/pages/users/user-page.component.ts
  2. 2
      src/Squidex/app/features/administration/pages/users/users-page.component.html
  3. 2
      src/Squidex/app/features/administration/state/event-consumers.state.ts
  4. 2
      src/Squidex/app/features/administration/state/users.state.ts
  5. 2
      src/Squidex/app/features/content/pages/contents/contents-page.component.html
  6. 2
      src/Squidex/app/features/content/shared/contents-selector.component.html
  7. 2
      src/Squidex/app/features/rules/pages/events/rule-events-page.component.html
  8. 9
      src/Squidex/app/framework/angular/code.component.ts
  9. 6
      src/Squidex/app/framework/angular/forms/checkbox-group.component.html
  10. 52
      src/Squidex/app/framework/angular/forms/checkbox-group.component.ts
  11. 14
      src/Squidex/app/framework/angular/forms/code-editor.component.ts
  12. 4
      src/Squidex/app/framework/angular/forms/control-errors.component.html
  13. 55
      src/Squidex/app/framework/angular/forms/control-errors.component.ts
  14. 5
      src/Squidex/app/framework/angular/forms/json-editor.component.ts
  15. 5
      src/Squidex/app/framework/angular/forms/progress-bar.component.ts
  16. 4
      src/Squidex/app/framework/angular/forms/stars.component.html
  17. 71
      src/Squidex/app/framework/angular/forms/stars.component.ts
  18. 6
      src/Squidex/app/framework/angular/forms/toggle.component.html
  19. 53
      src/Squidex/app/framework/angular/forms/toggle.component.ts
  20. 2
      src/Squidex/app/framework/angular/image-source.directive.ts
  21. 6
      src/Squidex/app/framework/angular/modals/dialog-renderer.component.html
  22. 103
      src/Squidex/app/framework/angular/modals/dialog-renderer.component.ts
  23. 4
      src/Squidex/app/framework/angular/modals/modal-dialog.component.ts
  24. 56
      src/Squidex/app/framework/angular/modals/onboarding-tooltip.component.ts
  25. 10
      src/Squidex/app/framework/angular/modals/root-view.component.ts
  26. 37
      src/Squidex/app/framework/angular/modals/tooltip.component.ts
  27. 4
      src/Squidex/app/framework/angular/pager.component.html
  28. 20
      src/Squidex/app/framework/angular/pager.component.ts
  29. 9
      src/Squidex/app/framework/angular/panel.component.ts
  30. 3
      src/Squidex/app/framework/angular/pipes/date-time.pipes.ts
  31. 2
      src/Squidex/app/framework/angular/pipes/money.pipe.ts
  32. 2
      src/Squidex/app/framework/angular/pipes/name.pipe.ts
  33. 12
      src/Squidex/app/framework/angular/shortcut.component.spec.ts
  34. 21
      src/Squidex/app/framework/angular/shortcut.component.ts
  35. 58
      src/Squidex/app/framework/angular/stateful.component.ts
  36. 2
      src/Squidex/app/framework/angular/title.component.ts
  37. 38
      src/Squidex/app/framework/angular/user-report.component.ts
  38. 2
      src/Squidex/app/framework/internal.ts
  39. 11
      src/Squidex/app/framework/state.ts
  40. 2
      src/Squidex/app/shared/components/assets-list.component.html
  41. 4
      src/Squidex/app/shared/state/apps.state.ts
  42. 2
      src/Squidex/app/shared/state/assets.state.ts
  43. 2
      src/Squidex/app/shared/state/contents.forms.ts
  44. 2
      src/Squidex/app/shared/state/schemas.state.ts

2
src/Squidex/app/features/administration/pages/users/user-page.component.ts

@ -42,7 +42,7 @@ export class UserPageComponent implements OnDestroy, OnInit {
this.selectedUserSubscription = this.selectedUserSubscription =
this.usersState.selectedUser this.usersState.selectedUser
.subscribe(selectedUser => { .subscribe(selectedUser => {
this.user = selectedUser; this.user = selectedUser!;
if (selectedUser) { if (selectedUser) {
this.userForm.load(selectedUser.user); this.userForm.load(selectedUser.user);

2
src/Squidex/app/features/administration/pages/users/users-page.component.html

@ -81,7 +81,7 @@
</div> </div>
<div class="grid-footer"> <div class="grid-footer">
<sqx-pager [pager]="usersState.usersPager | async" (prev)="goPrev()" (next)="goNext()"></sqx-pager> <sqx-pager [pager]="usersState.usersPager | async" (prevPage)="goPrev()" (nextPage)="goNext()"></sqx-pager>
</div> </div>
</ng-container> </ng-container>
</sqx-panel> </sqx-panel>

2
src/Squidex/app/features/administration/state/event-consumers.state.ts

@ -21,7 +21,7 @@ import { EventConsumerDto, EventConsumersService } from './../services/event-con
interface Snapshot { interface Snapshot {
eventConsumers: ImmutableArray<EventConsumerDto>; eventConsumers: ImmutableArray<EventConsumerDto>;
isLoaded?: false; isLoaded?: boolean;
} }
@Injectable() @Injectable()

2
src/Squidex/app/features/administration/state/users.state.ts

@ -98,7 +98,7 @@ interface Snapshot {
isLoaded?: boolean; isLoaded?: boolean;
selectedUser?: SnapshotUser; selectedUser?: SnapshotUser | null;
} }
@Injectable() @Injectable()

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

@ -120,7 +120,7 @@
</div> </div>
<div class="grid-footer"> <div class="grid-footer">
<sqx-pager [pager]="contentsState.contentsPager | async" (prev)="goPrev()" (next)="goNext()"></sqx-pager> <sqx-pager [pager]="contentsState.contentsPager | async" (prevPage)="goPrev()" (nextPage)="goNext()"></sqx-pager>
</div> </div>
</ng-container> </ng-container>

2
src/Squidex/app/features/content/shared/contents-selector.component.html

@ -63,7 +63,7 @@
</div> </div>
<div class="grid-footer"> <div class="grid-footer">
<sqx-pager [pager]="contentsState.contentsPager | async" (prev)="goPrev()" (next)="goNext()"></sqx-pager> <sqx-pager [pager]="contentsState.contentsPager | async" (prevPage)="goPrev()" (nextPage)="goNext()"></sqx-pager>
</div> </div>
</ng-container> </ng-container>

2
src/Squidex/app/features/rules/pages/events/rule-events-page.component.html

@ -90,7 +90,7 @@
</tbody> </tbody>
</table> </table>
<sqx-pager [pager]="ruleEventsState.ruleEventsPager | async" (prev)="goPrev()" (next)="goNext()"></sqx-pager> <sqx-pager [pager]="ruleEventsState.ruleEventsPager | async" (prevPage)="goPrev()" (nextPage)="goNext()"></sqx-pager>
</ng-container> </ng-container>
</sqx-panel> </sqx-panel>

9
src/Squidex/app/framework/angular/code.component.ts

@ -5,7 +5,9 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. * 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({ @Component({
selector: 'sqx-code', selector: 'sqx-code',
@ -13,5 +15,8 @@ import { ChangeDetectionStrategy, Component } from '@angular/core';
templateUrl: './code.component.html', templateUrl: './code.component.html',
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class CodeComponent { export class CodeComponent extends PureComponent {
constructor(changeDetector: ChangeDetectorRef) {
super(changeDetector);
}
} }

6
src/Squidex/app/framework/angular/forms/checkbox-group.component.html

@ -1,9 +1,9 @@
<span class="form-check" *ngFor="let value of values"> <span class="form-check" *ngFor="let value of values">
<input type="checkbox" class="form-check-input" id="{{control}}{{value}}" <input type="checkbox" class="form-check-input" id="{{snapshot.controlId}}{{value}}"
(blur)="blur()" (blur)="blur()"
(change)="check($event.target.checked, value)" (change)="check($event.target.checked, value)"
[checked]="isChecked(value)" [checked]="isChecked(value)"
[disabled]="isDisabled"> [disabled]="snapshot.isDisabled">
<label class="form-check-label" for="{{control}}{{value}}">{{value}}</label> <label class="form-check-label" for="{{snapshot.controlId}}{{value}}">{{value}}</label>
</span> </span>

52
src/Squidex/app/framework/angular/forms/checkbox-group.component.ts

@ -8,14 +8,22 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, forwardRef, Input } from '@angular/core'; import { ChangeDetectionStrategy, ChangeDetectorRef, Component, forwardRef, Input } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { Types } from '@app/framework/internal'; import {
MathHelper,
import { MathHelper } from '../../utils/math-helper'; StatefulComponent,
Types
} from '@app/framework/internal';
export const SQX_CHECKBOX_GROUP_CONTROL_VALUE_ACCESSOR: any = { export const SQX_CHECKBOX_GROUP_CONTROL_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => CheckboxGroupComponent), multi: true provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => CheckboxGroupComponent), multi: true
}; };
interface State {
checkedValues: string[];
controlId: string;
isDisabled: boolean;
}
@Component({ @Component({
selector: 'sqx-checkbox-group', selector: 'sqx-checkbox-group',
styleUrls: ['./checkbox-group.component.scss'], 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], providers: [SQX_CHECKBOX_GROUP_CONTROL_VALUE_ACCESSOR],
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class CheckboxGroupComponent implements ControlValueAccessor { export class CheckboxGroupComponent extends StatefulComponent<State> implements ControlValueAccessor {
private callChange = (v: any) => { /* NOOP */ }; private callChange = (v: any) => { /* NOOP */ };
private callTouched = () => { /* NOOP */ }; private callTouched = () => { /* NOOP */ };
private checkedValues: string[] = [];
@Input() @Input()
public values: string[] = []; public values: string[] = [];
public isDisabled = false; constructor(changeDetector: ChangeDetectorRef) {
super(changeDetector, {
public control = MathHelper.guid(); controlId: MathHelper.guid(),
checkedValues: [],
constructor( isDisabled: false
private readonly changeDetector: ChangeDetectorRef });
) {
} }
public writeValue(obj: any) { public writeValue(obj: any) {
this.checkedValues = Types.isArrayOfString(obj) ? obj.filter(x => this.values.indexOf(x) >= 0) : []; this.next({ checkedValues: Types.isArrayOfString(obj) ? obj.filter(x => this.values.indexOf(x) >= 0) : [] });
this.changeDetector.markForCheck();
} }
public setDisabledState(isDisabled: boolean): void { public setDisabledState(isDisabled: boolean): void {
this.isDisabled = isDisabled; this.next({ isDisabled });
this.changeDetector.markForCheck();
} }
public registerOnChange(fn: any) { public registerOnChange(fn: any) {
@ -65,16 +67,20 @@ export class CheckboxGroupComponent implements ControlValueAccessor {
} }
public check(isChecked: boolean, value: string) { public check(isChecked: boolean, value: string) {
let checkedValues = this.snapshot.checkedValues;
if (isChecked) { if (isChecked) {
this.checkedValues = [value, ...this.checkedValues]; checkedValues = [value, ...checkedValues];
} else { } 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) { public isChecked(value: string) {
return this.checkedValues.indexOf(value) >= 0; return this.snapshot.checkedValues.indexOf(value) >= 0;
} }
} }

14
src/Squidex/app/framework/angular/forms/code-editor.component.ts

@ -5,12 +5,16 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. * 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 { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import { debounceTime } from 'rxjs/operators'; import { debounceTime } from 'rxjs/operators';
import { ResourceLoaderService, Types } from '@app/framework/internal'; import {
PureComponent,
ResourceLoaderService,
Types
} from '@app/framework/internal';
declare var ace: any; declare var ace: any;
@ -25,7 +29,7 @@ export const SQX_JSCRIPT_EDITOR_CONTROL_VALUE_ACCESSOR: any = {
providers: [SQX_JSCRIPT_EDITOR_CONTROL_VALUE_ACCESSOR], providers: [SQX_JSCRIPT_EDITOR_CONTROL_VALUE_ACCESSOR],
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class CodeEditorComponent implements ControlValueAccessor, AfterViewInit { export class CodeEditorComponent extends PureComponent implements ControlValueAccessor, AfterViewInit {
private callChange = (v: any) => { /* NOOP */ }; private callChange = (v: any) => { /* NOOP */ };
private callTouched = () => { /* NOOP */ }; private callTouched = () => { /* NOOP */ };
private valueChanged = new Subject(); private valueChanged = new Subject();
@ -40,8 +44,12 @@ export class CodeEditorComponent implements ControlValueAccessor, AfterViewInit
public mode = 'ace/mode/javascript'; public mode = 'ace/mode/javascript';
constructor( constructor(
changeDetector: ChangeDetectorRef,
private readonly resourceLoader: ResourceLoaderService private readonly resourceLoader: ResourceLoaderService
) { ) {
super(changeDetector);
changeDetector.detach();
} }
public writeValue(obj: any) { public writeValue(obj: any) {

4
src/Squidex/app/framework/angular/forms/control-errors.component.html

@ -1,6 +1,6 @@
<div class="errors-container" *ngIf="errorMessages.length > 0" @fade> <div class="errors-container" *ngIf="snapshot.errorMessages.length > 0" @fade>
<div class="errors"> <div class="errors">
<span *ngFor="let message of errorMessages"> <span *ngFor="let message of snapshot.errorMessages">
{{message}} {{message}}
</span> </span>
</div> </div>

55
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 { ChangeDetectionStrategy, ChangeDetectorRef, Component, Host, Input, OnChanges, OnDestroy, Optional } from '@angular/core';
import { AbstractControl, FormGroupDirective } from '@angular/forms'; 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'; import { formatError } from './error-formatting';
interface State {
errorMessages: string[];
}
@Component({ @Component({
selector: 'sqx-control-errors', selector: 'sqx-control-errors',
styleUrls: ['./control-errors.component.scss'], styleUrls: ['./control-errors.component.scss'],
@ -22,10 +30,9 @@ import { formatError } from './error-formatting';
], ],
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class ControlErrorsComponent implements OnChanges, OnDestroy { export class ControlErrorsComponent extends StatefulComponent<State> implements OnChanges, OnDestroy {
private displayFieldName: string; private displayFieldName: string;
private control: AbstractControl; private control: AbstractControl;
private controlSubscription: Subscription | null = null;
private originalMarkAsTouched: any; private originalMarkAsTouched: any;
@Input() @Input()
@ -43,16 +50,20 @@ export class ControlErrorsComponent implements OnChanges, OnDestroy {
@Input() @Input()
public submitOnly = false; public submitOnly = false;
public errorMessages: string[] = []; constructor(changeDetector: ChangeDetectorRef,
@Optional() @Host() private readonly formGroupDirective: FormGroupDirective
constructor(
@Optional() @Host() private readonly formGroupDirective: FormGroupDirective,
private readonly changeDetector: ChangeDetectorRef
) { ) {
super(changeDetector, {
errorMessages: []
});
} }
public ngOnDestroy() { public ngOnDestroy() {
this.unsubscribe(); super.ngOnDestroy();
if (this.control && this.originalMarkAsTouched) {
this.control['markAsTouched'] = this.originalMarkAsTouched;
}
} }
public ngOnChanges() { public ngOnChanges() {
@ -75,16 +86,16 @@ export class ControlErrorsComponent implements OnChanges, OnDestroy {
} }
if (this.control !== control) { if (this.control !== control) {
this.unsubscribe(); this.ngOnDestroy();
this.control = control; this.control = control;
if (control) { if (control) {
this.controlSubscription = this.observe(
merge(control.valueChanges, control.statusChanges) merge(control.valueChanges, control.statusChanges)
.subscribe(() => { .subscribe(() => {
this.createMessages(); this.createMessages();
}); }));
this.originalMarkAsTouched = this.control.markAsTouched; this.originalMarkAsTouched = this.control.markAsTouched;
@ -101,18 +112,8 @@ export class ControlErrorsComponent implements OnChanges, OnDestroy {
this.createMessages(); this.createMessages();
} }
private unsubscribe() {
if (this.controlSubscription) {
this.controlSubscription.unsubscribe();
}
if (this.control && this.originalMarkAsTouched) {
this.control['markAsTouched'] = this.originalMarkAsTouched;
}
}
private createMessages() { private createMessages() {
const errors: string[] = []; const errorMessages: string[] = [];
if (this.control && this.control.invalid && ((this.control.touched && !this.submitOnly) || this.submitted) && this.control.errors) { if (this.control && this.control.invalid && ((this.control.touched && !this.submitOnly) || this.submitted) && this.control.errors) {
for (let key in <any>this.control.errors) { for (let key in <any>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); const message = formatError(this.displayFieldName, key, this.control.errors[key], this.control.value, this.errors);
if (message) { if (message) {
errors.push(message); errorMessages.push(message);
} }
} }
} }
} }
this.errorMessages = errors; this.next({ errorMessages });
this.changeDetector.markForCheck();
} }
} }

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

@ -5,7 +5,7 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. * 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 { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import { debounceTime } from 'rxjs/operators'; import { debounceTime } from 'rxjs/operators';
@ -37,9 +37,10 @@ export class JsonEditorComponent implements ControlValueAccessor, AfterViewInit
@ViewChild('editor') @ViewChild('editor')
public editor: ElementRef; public editor: ElementRef;
constructor( constructor(changeDetector: ChangeDetectorRef,
private readonly resourceLoader: ResourceLoaderService private readonly resourceLoader: ResourceLoaderService
) { ) {
changeDetector.detach();
} }
public writeValue(obj: any) { public writeValue(obj: any) {

5
src/Squidex/app/framework/angular/forms/progress-bar.component.ts

@ -5,7 +5,7 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. * 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'; import * as ProgressBar from 'progressbar.js';
@ -38,10 +38,11 @@ export class ProgressBarComponent implements OnChanges, OnInit {
@Input() @Input()
public value = 0; public value = 0;
constructor( constructor(changeDetector: ChangeDetectorRef,
private readonly element: ElementRef, private readonly element: ElementRef,
private readonly renderer: Renderer2 private readonly renderer: Renderer2
) { ) {
changeDetector.detach();
} }
public ngOnInit() { public ngOnInit() {

4
src/Squidex/app/framework/angular/forms/stars.component.html

@ -4,8 +4,8 @@
</div> </div>
<ng-container *ngIf="maximumStars > 0 && maximumStars <= 15"> <ng-container *ngIf="maximumStars > 0 && maximumStars <= 15">
<span class="stars" (mouseleave)="stopPreview()" [class.disabled]="isDisabled"> <span class="stars" (mouseleave)="stopPreview()" [class.disabled]="snapshot.isDisabled">
<span class="star" *ngFor="let star of starsArray" (mouseenter)="setPreview(star)" (click)="setValue(star)" [class.selected]="star <= stars"></span> <span class="star" *ngFor="let star of snapshot.starsArray" (mouseenter)="setPreview(star)" (click)="setValue(star)" [class.selected]="star <= snapshot.stars"></span>
</span> </span>
<button class="btn btn-text" [class.hidden]="!value" (click)="reset()">Clear</button> <button class="btn btn-text" [class.hidden]="!value" (click)="reset()">Clear</button>

71
src/Squidex/app/framework/angular/forms/stars.component.ts

@ -8,12 +8,21 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, forwardRef, Input } from '@angular/core'; import { ChangeDetectionStrategy, ChangeDetectorRef, Component, forwardRef, Input } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; 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 = { 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
}; };
interface State {
isDisabled: boolean;
stars: number;
starsArray: number[];
value: number | null;
}
@Component({ @Component({
selector: 'sqx-stars', selector: 'sqx-stars',
styleUrls: ['./stars.component.scss'], styleUrls: ['./stars.component.scss'],
@ -21,7 +30,7 @@ export const SQX_STARS_CONTROL_VALUE_ACCESSOR: any = {
providers: [SQX_STARS_CONTROL_VALUE_ACCESSOR], providers: [SQX_STARS_CONTROL_VALUE_ACCESSOR],
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class StarsComponent implements ControlValueAccessor { export class StarsComponent extends StatefulComponent<State> implements ControlValueAccessor {
private callChange = (v: any) => { /* NOOP */ }; private callChange = (v: any) => { /* NOOP */ };
private callTouched = () => { /* NOOP */ }; private callTouched = () => { /* NOOP */ };
private maximumStarsValue = 5; private maximumStarsValue = 5;
@ -33,11 +42,13 @@ export class StarsComponent implements ControlValueAccessor {
if (this.maximumStarsValue !== maxStars) { if (this.maximumStarsValue !== maxStars) {
this.maximumStarsValue = value; this.maximumStarsValue = value;
this.starsArray = []; const starsArray = [];
for (let i = 1; i <= value; i++) { 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; return this.maximumStarsValue;
} }
public isDisabled = false; constructor(changeDetector: ChangeDetectorRef) {
super(changeDetector, {
public stars: number; isDisabled: false,
public starsArray: number[] = [1, 2, 3, 4, 5]; stars: -1,
starsArray: [1, 2, 3, 4, 5],
public value: number | null = 1; value: 1
});
constructor(
private readonly changeDetector: ChangeDetectorRef
) {
} }
public writeValue(obj: any) { 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 { public setDisabledState(isDisabled: boolean): void {
this.isDisabled = isDisabled; this.next({ isDisabled });
this.changeDetector.markForCheck();
} }
public registerOnChange(fn: any) { public registerOnChange(fn: any) {
@ -77,32 +83,31 @@ export class StarsComponent implements ControlValueAccessor {
this.callTouched = fn; this.callTouched = fn;
} }
public setPreview(value: number) { public setPreview(stars: number) {
if (this.isDisabled) { if (this.snapshot.isDisabled) {
return; return;
} }
this.stars = value; this.next({ stars });
} }
public stopPreview() { public stopPreview() {
if (this.isDisabled) { if (this.snapshot.isDisabled) {
return; return;
} }
this.stars = this.value || 0; this.next(s => { s.stars = s.value || 0; });
} }
public reset() { public reset() {
if (this.isDisabled) { if (this.snapshot.isDisabled) {
return false; return false;
} }
if (this.value) { if (this.snapshot.value) {
this.value = null; this.next({ stars: -1, value: null });
this.stars = 0;
this.callChange(this.value); this.callChange(null);
this.callTouched(); this.callTouched();
} }
@ -110,14 +115,14 @@ export class StarsComponent implements ControlValueAccessor {
} }
public setValue(value: number) { public setValue(value: number) {
if (this.isDisabled) { if (this.snapshot.isDisabled) {
return false; return false;
} }
if (this.value !== value) { if (this.snapshot.value !== value) {
this.value = this.stars = value; this.next({ stars: value, value });
this.callChange(this.value); this.callChange(value);
this.callTouched(); this.callTouched();
} }

6
src/Squidex/app/framework/angular/forms/toggle.component.html

@ -1,6 +1,6 @@
<div class="toggle-container" (click)="changeState($event)" <div class="toggle-container" (click)="changeState($event)"
[class.disabled]="isDisabled" [class.disabled]="snapshot.isDisabled"
[class.checked]="isChecked === true" [class.checked]="snapshot.isChecked === true"
[class.unchecked]="isChecked === false"> [class.unchecked]="snapshot.isChecked === false">
<div class="toggle-button"></div> <div class="toggle-button"></div>
</div> </div>

53
src/Squidex/app/framework/angular/forms/toggle.component.ts

@ -5,47 +5,48 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. * 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 { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { Types } from '@app/framework/internal'; import { Types } from '@app/framework/internal';
import { StatefulComponent } from '../stateful.component';
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
}; };
interface State {
isDisabled: boolean;
isChecked: boolean | null;
}
@Component({ @Component({
selector: 'sqx-toggle', selector: 'sqx-toggle',
styleUrls: ['./toggle.component.scss'], styleUrls: ['./toggle.component.scss'],
templateUrl: './toggle.component.html', templateUrl: './toggle.component.html',
providers: [SQX_TOGGLE_CONTROL_VALUE_ACCESSOR], providers: [SQX_TOGGLE_CONTROL_VALUE_ACCESSOR]
changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class ToggleComponent implements ControlValueAccessor { export class ToggleComponent extends StatefulComponent<State> implements ControlValueAccessor {
private callChange = (v: any) => { /* NOOP */ }; private callChange = (v: any) => { /* NOOP */ };
private callTouched = () => { /* NOOP */ }; private callTouched = () => { /* NOOP */ };
@Input() @Input()
public threeStates = false; public threeStates = false;
public isChecked: boolean | null = null; constructor(changeDetector: ChangeDetectorRef) {
public isDisabled = false; super(changeDetector, {
isChecked: null,
constructor( isDisabled: false
private readonly changeDetector: ChangeDetectorRef });
) {
} }
public writeValue(obj: any) { public writeValue(obj: any) {
this.isChecked = Types.isBoolean(obj) ? obj : null; this.next({ isChecked: Types.isBoolean(obj) ? obj : null });
this.changeDetector.markForCheck();
} }
public setDisabledState(isDisabled: boolean): void { public setDisabledState(isDisabled: boolean): void {
this.isDisabled = isDisabled; this.next({ isDisabled });
this.changeDetector.markForCheck();
} }
public registerOnChange(fn: any) { public registerOnChange(fn: any) {
@ -57,23 +58,27 @@ export class ToggleComponent implements ControlValueAccessor {
} }
public changeState(event: MouseEvent) { public changeState(event: MouseEvent) {
if (this.isDisabled) { let { isDisabled, isChecked } = this.snapshot;
if (isDisabled) {
return; return;
} }
if (this.threeStates && (event.ctrlKey || event.shiftKey)) { if (this.threeStates && (event.ctrlKey || event.shiftKey)) {
if (this.isChecked) { if (isChecked) {
this.isChecked = null; isChecked = null;
} else if (this.isChecked === null) { } else if (isChecked === null) {
this.isChecked = false; isChecked = false;
} else { } else {
this.isChecked = true; isChecked = true;
} }
} else { } else {
this.isChecked = !(this.isChecked === true); isChecked = !(isChecked === true);
} }
this.callChange(this.isChecked); this.next({ isChecked });
this.callChange(isChecked);
this.callTouched(); this.callTouched();
} }
} }

2
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 { 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 } } = {}; const LAYOUT_CACHE: { [key: string]: { width: number, height: number } } = {};

6
src/Squidex/app/framework/angular/modals/dialog-renderer.component.html

@ -2,11 +2,11 @@
<sqx-modal-dialog *sqxModalView="dialogView;onRoot:true" showClose="false" (closed)="cancel()"> <sqx-modal-dialog *sqxModalView="dialogView;onRoot:true" showClose="false" (closed)="cancel()">
<ng-container title> <ng-container title>
{{dialogRequest?.title}} {{snapshot.dialogRequest?.title}}
</ng-container> </ng-container>
<ng-container content> <ng-container content>
{{dialogRequest?.text}} {{snapshot.dialogRequest?.text}}
</ng-container> </ng-container>
<ng-container footer> <ng-container footer>
@ -16,7 +16,7 @@
</sqx-modal-dialog> </sqx-modal-dialog>
<div class="notification-container notification-container-{{position}}"> <div class="notification-container notification-container-{{position}}">
<div class="alert alert-dismissible alert-{{notification.messageType}}" *ngFor="let notification of notifications" (click)="close(notification)" @fade> <div class="alert alert-dismissible alert-{{notification.messageType}}" *ngFor="let notification of snapshot.notifications" (click)="close(notification)" @fade>
<button type="button" class="close" data-dismiss="alert" (closed)="close(notification)">&times;</button> <button type="button" class="close" data-dismiss="alert" (closed)="close(notification)">&times;</button>
<span [innerHTML]="notification.message"></span> <span [innerHTML]="notification.message"></span>

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

@ -5,8 +5,8 @@
* Copyright (c) Sebastian Stehle. All rights r vbeserved * Copyright (c) Sebastian Stehle. All rights r vbeserved
*/ */
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy, OnInit } from '@angular/core'; import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit } from '@angular/core';
import { Subscription } from 'rxjs'; import { timer } from 'rxjs';
import { import {
DialogModel, DialogModel,
@ -16,6 +16,14 @@ import {
Notification Notification
} from '@app/framework/internal'; } from '@app/framework/internal';
import { StatefulComponent } from '../stateful.component';
interface State {
dialogRequest?: DialogRequest | null;
notifications: Notification[];
}
@Component({ @Component({
selector: 'sqx-dialog-renderer', selector: 'sqx-dialog-renderer',
styleUrls: ['./dialog-renderer.component.scss'], styleUrls: ['./dialog-renderer.component.scss'],
@ -25,89 +33,74 @@ import {
], ],
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class DialogRendererComponent implements OnDestroy, OnInit { export class DialogRendererComponent extends StatefulComponent<State> implements OnInit {
private dialogSubscription: Subscription;
private dialogsSubscription: Subscription;
private notificationsSubscription: Subscription;
public dialogView = new DialogModel();
public dialogRequest: DialogRequest | null = null;
public notifications: Notification[] = [];
@Input() @Input()
public position = 'bottomright'; public position = 'bottomright';
constructor( public dialogView = new DialogModel();
private readonly changeDetector: ChangeDetectorRef,
constructor(changeDetector: ChangeDetectorRef,
private readonly dialogs: DialogService private readonly dialogs: DialogService
) { ) {
} super(changeDetector, { notifications: [] });
public ngOnDestroy() {
this.notificationsSubscription.unsubscribe();
this.dialogSubscription.unsubscribe();
this.dialogsSubscription.unsubscribe();
} }
public ngOnInit() { public ngOnInit() {
this.dialogSubscription = this.observe(
this.dialogView.isOpen.subscribe(isOpen => { this.dialogView.isOpen.subscribe(isOpen => {
if (!isOpen) { if (!isOpen) {
this.cancel(); this.finishRequest(false);
this.changeDetector.detectChanges();
} }
}); }));
this.notificationsSubscription = this.observe(
this.dialogs.notifications.subscribe(notification => { this.dialogs.notifications.subscribe(notification => {
this.notifications.push(notification); this.next(state => {
state.notifications = [...state.notifications, notification];
});
if (notification.displayTime > 0) { if (notification.displayTime > 0) {
setTimeout(() => { this.observe(timer(notification.displayTime).subscribe(() => {
this.close(notification); this.close(notification);
}, notification.displayTime); }));
} }
}));
this.changeDetector.detectChanges(); this.observe(
});
this.dialogsSubscription =
this.dialogs.dialogs this.dialogs.dialogs
.subscribe(request => { .subscribe(request => {
this.cancel(); this.cancel();
this.dialogRequest = request; this.next(state => {
this.dialogView.show(); state.dialogRequest = request;
});
this.changeDetector.detectChanges(); }));
});
} }
public cancel() { public cancel() {
if (this.dialogRequest) { this.finishRequest(false);
this.dialogRequest.complete(false);
this.dialogRequest = null; this.dialogView.hide();
this.dialogView.hide();
}
} }
public confirm() { public confirm() {
if (this.dialogRequest) { this.finishRequest(true);
this.dialogRequest.complete(true);
this.dialogRequest = null;
this.dialogView.hide();
}
}
public close(notification: Notification) { this.dialogView.hide();
const index = this.notifications.indexOf(notification); }
if (index >= 0) { private finishRequest(value: boolean) {
this.notifications.splice(index, 1); 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);
});
} }
} }

4
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 { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, EventEmitter, Input, Output, ViewChild } from '@angular/core';
import { fadeAnimation } from './../animations'; import { fadeAnimation } from '@app/framework/internal';
@Component({ @Component({
selector: 'sqx-modal-dialog', selector: 'sqx-modal-dialog',
@ -16,7 +16,7 @@ import { fadeAnimation } from './../animations';
animations: [ animations: [
fadeAnimation fadeAnimation
], ],
changeDetection: ChangeDetectionStrategy.Default changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class ModalDialogComponent implements AfterViewInit { export class ModalDialogComponent implements AfterViewInit {
@Input() @Input()

56
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 { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy, OnInit, Renderer2 } from '@angular/core';
import { timer } from 'rxjs';
import { import {
fadeAnimation, fadeAnimation,
ModalModel, ModalModel,
OnboardingService, OnboardingService,
PureComponent,
Types Types
} from '@app/framework/internal'; } from '@app/framework/internal';
@ -23,11 +25,7 @@ import {
], ],
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class OnboardingTooltipComponent implements OnDestroy, OnInit { export class OnboardingTooltipComponent extends PureComponent implements OnDestroy, OnInit {
private showTimer: any;
private closeTimer: any;
private forMouseDownListener: Function | null;
public tooltipModal = new ModalModel(); public tooltipModal = new ModalModel();
@Input() @Input()
@ -42,56 +40,50 @@ export class OnboardingTooltipComponent implements OnDestroy, OnInit {
@Input() @Input()
public position = 'left'; public position = 'left';
constructor( constructor(changeDetector: ChangeDetectorRef,
private readonly changeDetector: ChangeDetectorRef,
private readonly onboardingService: OnboardingService, private readonly onboardingService: OnboardingService,
private readonly renderer: Renderer2 private readonly renderer: Renderer2
) { ) {
super(changeDetector);
} }
public ngOnDestroy() { public ngOnDestroy() {
clearTimeout(this.showTimer); super.ngOnDestroy();
clearTimeout(this.closeTimer);
this.tooltipModal.hide(); this.tooltipModal.hide();
if (this.forMouseDownListener) {
this.forMouseDownListener();
this.forMouseDownListener = null;
}
} }
public ngOnInit() { public ngOnInit() {
if (this.for && this.helpId && Types.isFunction(this.for.addEventListener)) { if (this.for && this.helpId && Types.isFunction(this.for.addEventListener)) {
this.showTimer = setTimeout(() => { this.observe(
if (this.onboardingService.shouldShow(this.helpId)) { timer(this.after).subscribe(() => {
const forRect = this.for.getBoundingClientRect(); 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;
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)) { const fromPoint = document.elementFromPoint(x, y);
this.tooltipModal.show();
this.changeDetector.markForCheck(); if (this.isSameOrParent(fromPoint)) {
this.tooltipModal.show();
this.closeTimer = setTimeout(() => { this.observe(
this.hideThis(); timer(10000).subscribe(() => {
}, 10000); 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.renderer.listen(this.for, 'mousedown', () => {
this.onboardingService.disable(this.helpId); this.onboardingService.disable(this.helpId);
this.hideThis(); this.hideThis();
}); }));
} }
} }

10
src/Squidex/app/framework/angular/modals/root-view.component.ts

@ -5,7 +5,9 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. * 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({ @Component({
selector: 'sqx-root-view', selector: 'sqx-root-view',
@ -13,7 +15,11 @@ import { ChangeDetectionStrategy, Component, ViewChild, ViewContainerRef } from
templateUrl: './root-view.component.html', templateUrl: './root-view.component.html',
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class RootViewComponent { export class RootViewComponent extends PureComponent {
@ViewChild('element', { read: ViewContainerRef }) @ViewChild('element', { read: ViewContainerRef })
public viewContainer: ViewContainerRef; public viewContainer: ViewContainerRef;
constructor(changeDetector: ChangeDetectorRef) {
super(changeDetector);
}
} }

37
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 { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy, OnInit, Renderer2 } from '@angular/core';
import { ModalModel } from './../../utils/modal-view'; import {
fadeAnimation,
import { fadeAnimation } from './../animations'; ModalModel,
PureComponent
} from '@app/framework/internal';
@Component({ @Component({
selector: 'sqx-tooltip', selector: 'sqx-tooltip',
@ -20,10 +22,7 @@ import { fadeAnimation } from './../animations';
], ],
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class TooltipComponent implements OnDestroy, OnInit { export class TooltipComponent extends PureComponent implements OnDestroy, OnInit {
private targetMouseEnterListener: any;
private targetMouseLeaveListener: any;
@Input() @Input()
public target: any; public target: any;
@ -32,35 +31,23 @@ export class TooltipComponent implements OnDestroy, OnInit {
public modal = new ModalModel(); public modal = new ModalModel();
constructor( constructor(changeDetector: ChangeDetectorRef,
private readonly changeDetector: ChangeDetectorRef,
private readonly renderer: Renderer2 private readonly renderer: Renderer2
) { ) {
} super(changeDetector);
public ngOnDestroy() {
if (this.targetMouseEnterListener) {
this.targetMouseEnterListener();
}
if (this.targetMouseLeaveListener) {
this.targetMouseLeaveListener();
}
} }
public ngOnInit() { public ngOnInit() {
if (this.target) { if (this.target) {
this.targetMouseEnterListener = this.observe(
this.renderer.listen(this.target, 'mouseenter', () => { this.renderer.listen(this.target, 'mouseenter', () => {
this.modal.show(); this.modal.show();
}));
this.changeDetector.markForCheck(); this.observe(
});
this.targetMouseLeaveListener =
this.renderer.listen(this.target, 'mouseleave', () => { this.renderer.listen(this.target, 'mouseleave', () => {
this.modal.hide(); this.modal.hide();
}); }));
} }
} }
} }

4
src/Squidex/app/framework/angular/pager.component.html

@ -2,10 +2,10 @@
<div class="float-right pagination"> <div class="float-right pagination">
<span class="pagination-text">{{pager.itemFirst}}-{{pager.itemLast}} of {{pager.numberOfItems}}</span> <span class="pagination-text">{{pager.itemFirst}}-{{pager.itemLast}} of {{pager.numberOfItems}}</span>
<button class="btn btn-text-secondary pagination-button" [disabled]="!pager.canGoPrev" (click)="prev.emit()"> <button class="btn btn-text-secondary pagination-button" [disabled]="!pager.canGoPrev" (click)="prevPage.emit()">
<i class="icon-angle-left"></i> <i class="icon-angle-left"></i>
</button> </button>
<button class="btn btn-text-secondary pagination-button" [disabled]="!pager.canGoNext" (click)="next.emit()"> <button class="btn btn-text-secondary pagination-button" [disabled]="!pager.canGoNext" (click)="nextPage.emit()">
<i class="icon-angle-right"></i> <i class="icon-angle-right"></i>
</button> </button>
</div> </div>

20
src/Squidex/app/framework/angular/pager.component.ts

@ -5,9 +5,9 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. * 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({ @Component({
selector: 'sqx-pager', selector: 'sqx-pager',
@ -15,16 +15,20 @@ import { Pager } from './../internal';
templateUrl: './pager.component.html', templateUrl: './pager.component.html',
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class PagerComponent { export class PagerComponent extends PureComponent {
@Output()
public nextPage = new EventEmitter();
@Output()
public prevPage = new EventEmitter();
@Input() @Input()
public pager: Pager; public pager: Pager;
@Input() @Input()
public hideWhenButtonsDisabled = false; public hideWhenButtonsDisabled = false;
@Output() constructor(changeDetector: ChangeDetectorRef) {
public next = new EventEmitter(); super(changeDetector);
}
@Output()
public prev = new EventEmitter();
} }

9
src/Squidex/app/framework/angular/panel.component.ts

@ -5,9 +5,9 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. * 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'; import { PanelContainerDirective } from './panel-container.directive';
@ -20,7 +20,7 @@ import { PanelContainerDirective } from './panel-container.directive';
], ],
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class PanelComponent implements AfterViewInit, OnDestroy, OnInit { export class PanelComponent extends PureComponent implements AfterViewInit, OnDestroy, OnInit {
private styleWidth: string; private styleWidth: string;
public renderWidth = 0; public renderWidth = 0;
@ -61,10 +61,11 @@ export class PanelComponent implements AfterViewInit, OnDestroy, OnInit {
@ViewChild('panel') @ViewChild('panel')
public panel: ElementRef<HTMLElement>; public panel: ElementRef<HTMLElement>;
constructor( constructor(changeDetector: ChangeDetectorRef,
private readonly container: PanelContainerDirective, private readonly container: PanelContainerDirective,
private readonly renderer: Renderer2 private readonly renderer: Renderer2
) { ) {
super(changeDetector);
} }
public ngOnDestroy() { public ngOnDestroy() {

3
src/Squidex/app/framework/angular/pipes/date-time.pipes.ts

@ -7,8 +7,7 @@
import { Pipe, PipeTransform } from '@angular/core'; import { Pipe, PipeTransform } from '@angular/core';
import { DateTime } from './../../utils/date-time'; import { DateTime, Duration } from '@app/framework/internal';
import { Duration } from './../../utils/duration';
@Pipe({ @Pipe({
name: 'sqxShortDate', name: 'sqxShortDate',

2
src/Squidex/app/framework/angular/pipes/money.pipe.ts

@ -7,7 +7,7 @@
import { Pipe, PipeTransform } from '@angular/core'; import { Pipe, PipeTransform } from '@angular/core';
import { CurrencyConfig, DecimalSeparatorConfig } from './../../configurations'; import { CurrencyConfig, DecimalSeparatorConfig } from '@app/framework/internal';
@Pipe({ @Pipe({
name: 'sqxMoney', name: 'sqxMoney',

2
src/Squidex/app/framework/angular/pipes/name.pipe.ts

@ -7,7 +7,7 @@
import { Pipe, PipeTransform } from '@angular/core'; import { Pipe, PipeTransform } from '@angular/core';
import { StringHelper } from './../../utils/string-helper'; import { StringHelper } from '@app/framework/internal';
@Pipe({ @Pipe({
name: 'sqxDisplayName', name: 'sqxDisplayName',

12
src/Squidex/app/framework/angular/shortcut.component.spec.ts

@ -18,13 +18,13 @@ describe('ShortcutComponent', () => {
}); });
it('should instantiate', () => { it('should instantiate', () => {
const shortcutComponent = new ShortcutComponent(shortcutService, new NgZone({})); const shortcutComponent = new ShortcutComponent(<any>{}, shortcutService, new NgZone({}));
expect(shortcutComponent).toBeDefined(); expect(shortcutComponent).toBeDefined();
}); });
it('should init without keys', () => { it('should init without keys', () => {
const shortcutComponent = new ShortcutComponent(shortcutService, new NgZone({})); const shortcutComponent = new ShortcutComponent(<any>{}, shortcutService, new NgZone({}));
shortcutComponent.keys = null!; shortcutComponent.keys = null!;
shortcutComponent.ngOnInit(); shortcutComponent.ngOnInit();
@ -33,7 +33,7 @@ describe('ShortcutComponent', () => {
}); });
it('should destroy without keys', () => { it('should destroy without keys', () => {
const shortcutComponent = new ShortcutComponent(shortcutService, new NgZone({})); const shortcutComponent = new ShortcutComponent(<any>{}, shortcutService, new NgZone({}));
shortcutComponent.keys = null!; shortcutComponent.keys = null!;
shortcutComponent.ngOnDestroy(); shortcutComponent.ngOnDestroy();
@ -42,7 +42,7 @@ describe('ShortcutComponent', () => {
}); });
it('should raise event when triggered', () => { it('should raise event when triggered', () => {
const shortcutComponent = new ShortcutComponent(shortcutService, new NgZone({})); const shortcutComponent = new ShortcutComponent(<any>{}, shortcutService, new NgZone({}));
let isTriggered = false; let isTriggered = false;
@ -56,7 +56,7 @@ describe('ShortcutComponent', () => {
}); });
it('should not raise event when triggered but disabled', () => { it('should not raise event when triggered but disabled', () => {
const shortcutComponent = new ShortcutComponent(shortcutService, new NgZone({})); const shortcutComponent = new ShortcutComponent(<any>{}, shortcutService, new NgZone({}));
let isTriggered = false; let isTriggered = false;
@ -71,7 +71,7 @@ describe('ShortcutComponent', () => {
}); });
it('should not raise event when triggered but destroyed', () => { it('should not raise event when triggered but destroyed', () => {
const shortcutComponent = new ShortcutComponent(shortcutService, new NgZone({})); const shortcutComponent = new ShortcutComponent(<any>{}, shortcutService, new NgZone({}));
let isTriggered = false; let isTriggered = false;

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

@ -5,16 +5,17 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. * 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({ @Component({
selector: 'sqx-shortcut', selector: 'sqx-shortcut',
template: '', template: ''
changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class ShortcutComponent implements OnDestroy, OnInit { export class ShortcutComponent extends PureComponent implements OnDestroy, OnInit {
private lastKeys: string;
@Input() @Input()
public keys: string; public keys: string;
@ -24,12 +25,14 @@ export class ShortcutComponent implements OnDestroy, OnInit {
@Output() @Output()
public trigger = new EventEmitter(); public trigger = new EventEmitter();
private lastKeys: string;
constructor( constructor(
changeDetector: ChangeDetectorRef,
private readonly shortcutService: ShortcutService, private readonly shortcutService: ShortcutService,
private readonly zone: NgZone private readonly zone: NgZone
) { ) {
super(changeDetector);
changeDetector.detach();
} }
public ngOnDestroy() { public ngOnDestroy() {
@ -42,10 +45,10 @@ export class ShortcutComponent implements OnDestroy, OnInit {
this.lastKeys = this.keys; this.lastKeys = this.keys;
if (this.lastKeys) { if (this.lastKeys) {
this.shortcutService.on(this.lastKeys, e => { this.shortcutService.on(this.lastKeys, () => {
if (!this.disabled) { if (!this.disabled) {
this.zone.run(() => { this.zone.run(() => {
this.trigger.next(e); this.trigger.next();
}); });
} }

58
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<T> extends State<T> 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<any> {
constructor(changeDetector: ChangeDetectorRef) {
super(changeDetector, {});
}
}

2
src/Squidex/app/framework/angular/title.component.ts

@ -7,7 +7,7 @@
import { ChangeDetectionStrategy, Component, Input, OnChanges } from '@angular/core'; import { ChangeDetectionStrategy, Component, Input, OnChanges } from '@angular/core';
import { TitleService } from './../services/title.service'; import { TitleService } from '@app/framework/internal';
@Component({ @Component({
selector: 'sqx-title', selector: 'sqx-title',

38
src/Squidex/app/framework/angular/user-report.component.ts

@ -5,36 +5,36 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. * 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 {
import { ResourceLoaderService } from './../services/resource-loader.service'; PureComponent,
ResourceLoaderService,
UserReportConfig
} from '@app/framework/internal';
@Component({ @Component({
selector: 'sqx-user-report', selector: 'sqx-user-report',
template: '', template: ''
changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class UserReportComponent implements OnDestroy, OnInit { export class UserReportComponent extends PureComponent implements OnDestroy, OnInit {
private loadingTimer: any; constructor(changeDetector: ChangeDetectorRef,
private readonly config: UserReportConfig,
constructor(config: UserReportConfig, changeDetector: ChangeDetectorRef,
private readonly resourceLoader: ResourceLoaderService private readonly resourceLoader: ResourceLoaderService
) { ) {
changeDetector.detach(); super(changeDetector);
window['_urq'] = window['_urq'] || [];
window['_urq'].push(['initSite', config.siteId]);
}
public ngOnDestroy() { changeDetector.detach();
clearTimeout(this.loadingTimer);
} }
public ngOnInit() { public ngOnInit() {
this.loadingTimer = window['_urq'] = window['_urq'] || [];
setTimeout(() => { window['_urq'].push(['initSite', this.config.siteId]);
this.observe(
timer(4000).subscribe(() => {
this.resourceLoader.loadScript('https://cdn.userreport.com/userreport.js'); this.resourceLoader.loadScript('https://cdn.userreport.com/userreport.js');
}, 4000); }));
} }
} }

2
src/Squidex/app/framework/internal.ts

@ -35,4 +35,6 @@ export * from './utils/string-helper';
export * from './utils/types'; export * from './utils/types';
export * from './utils/version'; export * from './utils/version';
export * from './angular/stateful.component';
export * from './configurations'; export * from './configurations';

11
src/Squidex/app/framework/state.ts

@ -15,7 +15,7 @@ import { fullValue} from './angular/forms/forms-helper';
export interface FormState { export interface FormState {
submitted: boolean; submitted: boolean;
error?: string; error?: string | null;
} }
export class Form<T extends AbstractControl> { export class Form<T extends AbstractControl> {
@ -136,11 +136,14 @@ export class State<T extends {}> {
this.next(this.initialState); this.next(this.initialState);
} }
public next(update: ((v: T) => T) | object) { public next(update: ((v: T) => T | void) | Partial<T>) {
if (Types.isFunction(update)) { 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 { } else {
this.state.next(Object.assign({}, this.snapshot, update)); this.state.next({ ...this.snapshot, ...update });
} }
} }
} }

2
src/Squidex/app/shared/components/assets-list.component.html

@ -35,4 +35,4 @@
</ng-container> </ng-container>
</div> </div>
<sqx-pager [hideWhenButtonsDisabled]="true" [pager]="state.assetsPager | async" (prev)="goPrev()" (next)="goNext()"></sqx-pager> <sqx-pager [hideWhenButtonsDisabled]="true" [pager]="state.assetsPager | async" (prevPage)="goPrev()" (nextPage)="goNext()"></sqx-pager>

4
src/Squidex/app/shared/state/apps.state.ts

@ -67,9 +67,9 @@ export class AppsState extends State<Snapshot> {
public load(): Observable<any> { public load(): Observable<any> {
return this.appsService.getApps().pipe( return this.appsService.getApps().pipe(
tap(dtos => { tap((dto: AppDto[]) => {
this.next(s => { this.next(s => {
const apps = ImmutableArray.of(dtos); const apps = ImmutableArray.of(dto);
return { ...s, apps }; return { ...s, apps };
}); });

2
src/Squidex/app/shared/state/assets.state.ts

@ -28,7 +28,7 @@ interface Snapshot {
assetsPager: Pager; assetsPager: Pager;
assetsQuery?: string; assetsQuery?: string;
isLoaded?: false; isLoaded?: boolean;
} }
@Injectable() @Injectable()

2
src/Squidex/app/shared/state/contents.forms.ts

@ -147,7 +147,7 @@ export class FieldValidatorsFactory implements FieldPropertiesVisitor<ValidatorF
public visitNumber(properties: NumberFieldPropertiesDto): ValidatorFn[] { public visitNumber(properties: NumberFieldPropertiesDto): ValidatorFn[] {
const validators: ValidatorFn[] = [ const validators: ValidatorFn[] = [
ValidatorsEx.betweenLength(properties.minValue, properties.maxValue) ValidatorsEx.between(properties.minValue, properties.maxValue)
]; ];
if (properties.allowedValues && properties.allowedValues.length > 0) { if (properties.allowedValues && properties.allowedValues.length > 0) {

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

@ -325,7 +325,7 @@ export class SchemasState extends State<Snapshot> {
private replaceSchema(schema: SchemaDto) { private replaceSchema(schema: SchemaDto) {
return this.next(s => { return this.next(s => {
const schemas = s.schemas.replaceBy('id', schema).sortByStringAsc(x => x.displayName); 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); const categories = buildCategories(s.categories, schemas);

Loading…
Cancel
Save