diff --git a/src/Squidex/app/features/content/shared/assets-editor.component.html b/src/Squidex/app/features/content/shared/assets-editor.component.html index 176b2dfb9..11661283c 100644 --- a/src/Squidex/app/features/content/shared/assets-editor.component.html +++ b/src/Squidex/app/features/content/shared/assets-editor.component.html @@ -1,4 +1,4 @@ -
+
@@ -8,10 +8,10 @@
- -
@@ -22,10 +22,10 @@
- -
@@ -33,14 +33,14 @@
-
-
+
diff --git a/src/Squidex/app/features/content/shared/assets-editor.component.ts b/src/Squidex/app/features/content/shared/assets-editor.component.ts index 5a59e5951..3eb4eff53 100644 --- a/src/Squidex/app/features/content/shared/assets-editor.component.ts +++ b/src/Squidex/app/features/content/shared/assets-editor.component.ts @@ -7,9 +7,8 @@ // tslint:disable:prefer-for-of -import { ChangeDetectionStrategy, ChangeDetectorRef, Component, forwardRef, OnDestroy, OnInit } from '@angular/core'; -import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; -import { Subscription } from 'rxjs'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, forwardRef, OnInit } from '@angular/core'; +import { NG_VALUE_ACCESSOR } from '@angular/forms'; import { AppsState, @@ -19,6 +18,7 @@ import { ImmutableArray, LocalStoreService, MessageBus, + StatefulControlComponent, Types } from '@app/shared'; @@ -34,6 +34,14 @@ class AssetUpdated { } } +interface State { + newAssets: ImmutableArray; + + oldAssets: ImmutableArray; + + isListView: boolean; +} + @Component({ selector: 'sqx-assets-editor', styleUrls: ['./assets-editor.component.scss'], @@ -41,39 +49,32 @@ class AssetUpdated { providers: [SQX_ASSETS_EDITOR_CONTROL_VALUE_ACCESSOR], changeDetection: ChangeDetectionStrategy.OnPush }) -export class AssetsEditorComponent implements ControlValueAccessor, OnInit, OnDestroy { - private callChange = (v: any) => { /* NOOP */ }; - private callTouched = () => { /* NOOP */ }; - private subscription: Subscription; - +export class AssetsEditorComponent extends StatefulControlComponent implements OnInit { public assetsDialog = new DialogModel(); - public newAssets = ImmutableArray.empty(); - public oldAssets = ImmutableArray.empty(); - - public isListView = false; - public isDisabled = false; - - constructor( + constructor(changeDetector: ChangeDetectorRef, private readonly appsState: AppsState, private readonly assetsService: AssetsService, - private readonly changeDetector: ChangeDetectorRef, private readonly localStore: LocalStoreService, private readonly messageBus: MessageBus ) { - this.isListView = this.localStore.getBoolean('squidex.assets.list-view'); + super(changeDetector, { + oldAssets: ImmutableArray.empty(), + newAssets: ImmutableArray.empty(), + isListView: localStore.getBoolean('squidex.assets.list-view') + }); } public writeValue(obj: any) { if (Types.isArrayOfString(obj)) { - if (!Types.isEquals(obj, this.oldAssets.map(x => x.id).values)) { + if (!Types.isEquals(obj, this.snapshot.oldAssets.map(x => x.id).values)) { const assetIds: string[] = obj; this.assetsService.getAssets(this.appsState.appName, 0, 0, undefined, undefined, obj) .subscribe(dtos => { this.setAssets(ImmutableArray.of(assetIds.map(id => dtos.items.find(x => x.id === id)!).filter(a => !!a))); - if (this.oldAssets.length !== assetIds.length) { + if (this.snapshot.oldAssets.length !== assetIds.length) { this.updateValue(); } }, () => { @@ -89,42 +90,18 @@ export class AssetsEditorComponent implements ControlValueAccessor, OnInit, OnDe this.messageBus.emit(new AssetUpdated(asset, this)); } - public ngOnDestroy() { - this.subscription.unsubscribe(); - } - public ngOnInit() { - this.subscription = + this.observe( this.messageBus.of(AssetUpdated) .subscribe(event => { if (event.source !== this) { - this.setAssets(this.oldAssets.replaceBy('id', event.asset)); + this.setAssets(this.snapshot.oldAssets.replaceBy('id', event.asset)); } - }); - } - - public setAssets(asset: ImmutableArray) { - this.oldAssets = asset; - - this.changeDetector.markForCheck(); - } - - public setDisabledState(isDisabled: boolean): void { - this.isDisabled = isDisabled; - - this.changeDetector.markForCheck(); + })); } - public noop() { - return; - } - - public registerOnChange(fn: any) { - this.callChange = fn; - } - - public registerOnTouched(fn: any) { - this.callTouched = fn; + public setAssets(oldAssets: ImmutableArray) { + this.next(s => ({ ...s, oldAssets })); } public pasteFiles(event: ClipboardEvent) { @@ -132,7 +109,7 @@ export class AssetsEditorComponent implements ControlValueAccessor, OnInit, OnDe const file = event.clipboardData.items[i].getAsFile(); if (file) { - this.newAssets = this.newAssets.pushFront(file); + this.next(s => ({ ...s, newAssets: s.newAssets.pushFront(file) })); } } } @@ -142,15 +119,13 @@ export class AssetsEditorComponent implements ControlValueAccessor, OnInit, OnDe const file = files[i]; if (file) { - this.newAssets = this.newAssets.pushFront(file); + this.next(s => ({ ...s, newAssets: s.newAssets.pushFront(file) })); } } } public selectAssets(assets: AssetDto[]) { - for (let asset of assets) { - this.oldAssets = this.oldAssets.push(asset); - } + this.setAssets(this.snapshot.oldAssets.push(...assets)); if (assets.length > 0) { this.updateValue(); @@ -161,8 +136,11 @@ export class AssetsEditorComponent implements ControlValueAccessor, OnInit, OnDe public addAsset(file: File, asset: AssetDto) { if (asset && file) { - this.newAssets = this.newAssets.remove(file); - this.oldAssets = this.oldAssets.pushFront(asset); + this.next(s => ({ + ...s, + newAssets: s.newAssets.remove(file), + oldAssets: s.oldAssets.pushFront(asset) + })); this.updateValue(); } @@ -170,7 +148,7 @@ export class AssetsEditorComponent implements ControlValueAccessor, OnInit, OnDe public sortAssets(assets: AssetDto[]) { if (assets) { - this.oldAssets = ImmutableArray.of(assets); + this.setAssets(ImmutableArray.of(assets)); this.updateValue(); } @@ -178,24 +156,24 @@ export class AssetsEditorComponent implements ControlValueAccessor, OnInit, OnDe public removeLoadedAsset(asset: AssetDto) { if (asset) { - this.oldAssets = this.oldAssets.remove(asset); + this.setAssets(this.snapshot.oldAssets.remove(asset)); this.updateValue(); } } public removeLoadingAsset(file: File) { - this.newAssets = this.newAssets.remove(file); + this.next(s => ({ ...s, newAssets: s.newAssets.remove(file) })); } public changeView(isListView: boolean) { - this.isListView = isListView; + this.next(s => ({ ...s, isListView })); this.localStore.setBoolean('squidex.assets.list-view', isListView); } private updateValue() { - let ids: string[] | null = this.oldAssets.values.map(x => x.id); + let ids: string[] | null = this.snapshot.oldAssets.values.map(x => x.id); if (ids.length === 0) { ids = null; @@ -203,11 +181,9 @@ export class AssetsEditorComponent implements ControlValueAccessor, OnInit, OnDe this.callTouched(); this.callChange(ids); - - this.changeDetector.markForCheck(); } - public trackByAsset(index: number, asset: AssetDto) { + public trackByAsset(asset: AssetDto) { return asset.id; } } \ No newline at end of file diff --git a/src/Squidex/app/features/content/shared/preview-button.component.ts b/src/Squidex/app/features/content/shared/preview-button.component.ts index 215500f7d..e47e393fd 100644 --- a/src/Squidex/app/features/content/shared/preview-button.component.ts +++ b/src/Squidex/app/features/content/shared/preview-button.component.ts @@ -20,10 +20,10 @@ import { selector: 'sqx-preview-button', styleUrls: ['./preview-button.component.scss'], templateUrl: './preview-button.component.html', - changeDetection: ChangeDetectionStrategy.OnPush, animations: [ fadeAnimation - ] + ], + changeDetection: ChangeDetectionStrategy.OnPush }) export class PreviewButtonComponent implements OnInit { @Input() diff --git a/src/Squidex/app/features/content/shared/references-editor.component.html b/src/Squidex/app/features/content/shared/references-editor.component.html index 7fbb63a25..2bc315e36 100644 --- a/src/Squidex/app/features/content/shared/references-editor.component.html +++ b/src/Squidex/app/features/content/shared/references-editor.component.html @@ -1,27 +1,27 @@ -
- +
+
Click here to link content items.
- - +
-
+
Schema not found or not configured yet.
@@ -30,7 +30,7 @@ \ No newline at end of file diff --git a/src/Squidex/app/features/content/shared/references-editor.component.ts b/src/Squidex/app/features/content/shared/references-editor.component.ts index bbe28ba16..8485eb2f9 100644 --- a/src/Squidex/app/features/content/shared/references-editor.component.ts +++ b/src/Squidex/app/features/content/shared/references-editor.component.ts @@ -8,7 +8,7 @@ // tslint:disable:prefer-for-of import { ChangeDetectionStrategy, ChangeDetectorRef, Component, forwardRef, Input, OnInit } from '@angular/core'; -import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { NG_VALUE_ACCESSOR } from '@angular/forms'; import { AppLanguageDto, @@ -20,6 +20,7 @@ import { MathHelper, SchemaDetailsDto, SchemasService, + StatefulControlComponent, Types } from '@app/shared'; @@ -27,6 +28,13 @@ export const SQX_REFERENCES_EDITOR_CONTROL_VALUE_ACCESSOR: any = { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => ReferencesEditorComponent), multi: true }; +interface State { + schema?: SchemaDetailsDto; + schemaInvalid: boolean; + + contentItems: ImmutableArray; +} + @Component({ selector: 'sqx-references-editor', styleUrls: ['./references-editor.component.scss'], @@ -34,10 +42,7 @@ export const SQX_REFERENCES_EDITOR_CONTROL_VALUE_ACCESSOR: any = { providers: [SQX_REFERENCES_EDITOR_CONTROL_VALUE_ACCESSOR], changeDetection: ChangeDetectionStrategy.OnPush }) -export class ReferencesEditorComponent implements ControlValueAccessor, OnInit { - private callChange = (v: any) => { /* NOOP */ }; - private callTouched = () => { /* NOOP */ }; - +export class ReferencesEditorComponent extends StatefulControlComponent implements OnInit { @Input() public schemaId: string; @@ -49,49 +54,41 @@ export class ReferencesEditorComponent implements ControlValueAccessor, OnInit { public selectorDialog = new DialogModel(); - public schema: SchemaDetailsDto; - - public contentItems = ImmutableArray.empty(); - - public isDisabled = false; - public isInvalidSchema = false; - - constructor( + constructor(changeDetector: ChangeDetectorRef, private readonly appsState: AppsState, - private readonly changeDetector: ChangeDetectorRef, private readonly contentsService: ContentsService, private readonly schemasService: SchemasService ) { + super(changeDetector, { + schemaInvalid: false, + contentItems: ImmutableArray.empty() + }); } public ngOnInit() { if (this.schemaId === MathHelper.EMPTY_GUID) { - this.isInvalidSchema = true; + this.next(s => ({ ...s, schemaInvalid: true })); return; } this.schemasService.getSchema(this.appsState.appName, this.schemaId) - .subscribe(dto => { - this.schema = dto; - - this.changeDetector.markForCheck(); + .subscribe(schema => { + this.next(s => ({ ...s, schema })); }, () => { - this.isInvalidSchema = true; - - this.changeDetector.markForCheck(); + this.next(s => ({ ...s, schemaInvalid: true })); }); } public writeValue(obj: any) { if (Types.isArrayOfString(obj)) { - if (!Types.isEquals(obj, this.contentItems.map(x => x.id).values)) { + if (!Types.isEquals(obj, this.snapshot.contentItems.map(x => x.id).values)) { const contentIds: string[] = obj; this.contentsService.getContents(this.appsState.appName, this.schemaId, 10000, 0, undefined, contentIds) .subscribe(dtos => { this.setContentItems(ImmutableArray.of(contentIds.map(id => dtos.items.find(c => c.id === id)!).filter(r => !!r))); - if (this.contentItems.length !== contentIds.length) { + if (this.snapshot.contentItems.length !== contentIds.length) { this.updateValue(); } }, () => { @@ -103,29 +100,13 @@ export class ReferencesEditorComponent implements ControlValueAccessor, OnInit { } } - public setContentItems(contents: ImmutableArray) { - this.contentItems = contents; - - this.changeDetector.markForCheck(); - } - - public setDisabledState(isDisabled: boolean): void { - this.isDisabled = isDisabled; - - this.changeDetector.markForCheck(); - } - - public registerOnChange(fn: any) { - this.callChange = fn; - } - - public registerOnTouched(fn: any) { - this.callTouched = fn; + public setContentItems(contentItems: ImmutableArray) { + this.next(s => ({ ...s, contentItems })); } public select(contents: ContentDto[]) { for (let content of contents) { - this.contentItems = this.contentItems.push(content); + this.setContentItems(this.snapshot.contentItems.push(content)); } if (contents.length > 0) { @@ -137,7 +118,7 @@ export class ReferencesEditorComponent implements ControlValueAccessor, OnInit { public remove(content: ContentDto) { if (content) { - this.contentItems = this.contentItems.remove(content); + this.setContentItems(this.snapshot.contentItems.remove(content)); this.updateValue(); } @@ -145,14 +126,14 @@ export class ReferencesEditorComponent implements ControlValueAccessor, OnInit { public sort(contents: ContentDto[]) { if (contents) { - this.contentItems = ImmutableArray.of(contents); + this.setContentItems(ImmutableArray.of(contents)); this.updateValue(); } } private updateValue() { - let ids: string[] | null = this.contentItems.values.map(x => x.id); + let ids: string[] | null = this.snapshot.contentItems.values.map(x => x.id); if (ids.length === 0) { ids = null; @@ -160,7 +141,5 @@ export class ReferencesEditorComponent implements ControlValueAccessor, OnInit { this.callTouched(); this.callChange(ids); - - this.changeDetector.markForCheck(); } } \ No newline at end of file diff --git a/src/Squidex/app/framework/angular/code.component.ts b/src/Squidex/app/framework/angular/code.component.ts index 7ae539d76..f78d5b637 100644 --- a/src/Squidex/app/framework/angular/code.component.ts +++ b/src/Squidex/app/framework/angular/code.component.ts @@ -5,9 +5,7 @@ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ -import { ChangeDetectionStrategy, ChangeDetectorRef, Component } from '@angular/core'; - -import { PureComponent } from '@app/framework/internal'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; @Component({ selector: 'sqx-code', @@ -15,8 +13,4 @@ import { PureComponent } from '@app/framework/internal'; templateUrl: './code.component.html', changeDetection: ChangeDetectionStrategy.OnPush }) -export class CodeComponent extends PureComponent { - constructor(changeDetector: ChangeDetectorRef) { - super(changeDetector); - } -} \ No newline at end of file +export class CodeComponent { } \ No newline at end of file diff --git a/src/Squidex/app/framework/angular/forms/autocomplete.component.html b/src/Squidex/app/framework/angular/forms/autocomplete.component.html index 882956d72..9f4a662c3 100644 --- a/src/Squidex/app/framework/angular/forms/autocomplete.component.html +++ b/src/Squidex/app/framework/angular/forms/autocomplete.component.html @@ -5,13 +5,13 @@ autocorrect="off" autocapitalize="off"> -
-
+
+ [sqxScrollActive]="i === snapshot.suggestedIndex"> {{item}} diff --git a/src/Squidex/app/framework/angular/forms/autocomplete.component.ts b/src/Squidex/app/framework/angular/forms/autocomplete.component.ts index 164f74ec3..580593e34 100644 --- a/src/Squidex/app/framework/angular/forms/autocomplete.component.ts +++ b/src/Squidex/app/framework/angular/forms/autocomplete.component.ts @@ -5,11 +5,13 @@ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ -import { ChangeDetectionStrategy, Component, ContentChild, ElementRef, forwardRef, Input, OnDestroy, OnInit, TemplateRef, ViewChild } from '@angular/core'; -import { ControlValueAccessor, FormControl, NG_VALUE_ACCESSOR } from '@angular/forms'; -import { Observable, of, Subscription } from 'rxjs'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChild, ElementRef, forwardRef, Input, OnInit, TemplateRef, ViewChild } from '@angular/core'; +import { FormControl, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { Observable, of } from 'rxjs'; import { catchError, debounceTime, distinctUntilChanged, filter, map, switchMap, tap } from 'rxjs/operators'; +import { StatefulControlComponent } from '@app/shared'; + export interface AutocompleteSource { find(query: string): Observable; } @@ -23,6 +25,11 @@ export const SQX_AUTOCOMPLETE_CONTROL_VALUE_ACCESSOR: any = { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => AutocompleteComponent), multi: true }; +interface State { + suggestedItems: any[]; + suggestedIndex: number; +} + @Component({ selector: 'sqx-autocomplete', styleUrls: ['./autocomplete.component.scss'], @@ -30,11 +37,7 @@ export const SQX_AUTOCOMPLETE_CONTROL_VALUE_ACCESSOR: any = { providers: [SQX_AUTOCOMPLETE_CONTROL_VALUE_ACCESSOR], changeDetection: ChangeDetectionStrategy.OnPush }) -export class AutocompleteComponent implements ControlValueAccessor, OnDestroy, OnInit { - private subscription: Subscription; - private callChange = (v: any) => { /* NOOP */ }; - private callTouched = () => { /* NOOP */ }; - +export class AutocompleteComponent extends StatefulControlComponent implements OnInit { @Input() public source: AutocompleteSource; @@ -53,17 +56,17 @@ export class AutocompleteComponent implements ControlValueAccessor, OnDestroy, O @ViewChild('input') public inputControl: ElementRef; - public suggestedItems: any[] = []; - public suggestedIndex = -1; - public queryInput = new FormControl(); - public ngOnDestroy() { - this.subscription.unsubscribe(); + constructor(changeDetector: ChangeDetectorRef) { + super(changeDetector, { + suggestedItems: [], + suggestedIndex: -1 + }); } public ngOnInit() { - this.subscription = + this.observe( this.queryInput.valueChanges.pipe( tap(query => { this.callChange(query); @@ -80,9 +83,12 @@ export class AutocompleteComponent implements ControlValueAccessor, OnDestroy, O filter(query => !!query && !!this.source), switchMap(query => this.source.find(query)), catchError(() => of([]))) .subscribe(items => { - this.suggestedIndex = -1; - this.suggestedItems = items || []; - }); + this.next(s => ({ + ...s, + suggestedIndex: -1, + suggestedItems: items || [] + })); + })); } public onKeyDown(event: KeyboardEvent) { @@ -98,7 +104,7 @@ export class AutocompleteComponent implements ControlValueAccessor, OnDestroy, O this.reset(); return false; case KEY_ENTER: - if (this.suggestedItems.length > 0 && this.selectItem()) { + if (this.snapshot.suggestedItems.length > 0 && this.selectItem()) { return false; } break; @@ -149,11 +155,11 @@ export class AutocompleteComponent implements ControlValueAccessor, OnDestroy, O public selectItem(selection: any | null = null): boolean { if (!selection) { - selection = this.suggestedItems[this.suggestedIndex]; + selection = this.snapshot.suggestedItems[this.snapshot.suggestedIndex]; } - if (!selection && this.suggestedItems.length === 1) { - selection = this.suggestedItems[0]; + if (!selection && this.snapshot.suggestedItems.length === 1) { + selection = this.snapshot.suggestedItems[0]; } if (selection) { @@ -174,24 +180,24 @@ export class AutocompleteComponent implements ControlValueAccessor, OnDestroy, O return false; } - public selectIndex(selection: number) { - if (selection < 0) { - selection = 0; + public selectIndex(suggestedIndex: number) { + if (suggestedIndex < 0) { + suggestedIndex = 0; } - if (selection >= this.suggestedItems.length) { - selection = this.suggestedItems.length - 1; + if (suggestedIndex >= this.snapshot.suggestedItems.length) { + suggestedIndex = this.snapshot.suggestedItems.length - 1; } - this.suggestedIndex = selection; + this.next(s => ({ ...s, suggestedIndex })); } private up() { - this.selectIndex(this.suggestedIndex - 1); + this.selectIndex(this.snapshot.suggestedIndex - 1); } private down() { - this.selectIndex(this.suggestedIndex + 1); + this.selectIndex(this.snapshot.suggestedIndex + 1); } private resetForm() { @@ -199,7 +205,10 @@ export class AutocompleteComponent implements ControlValueAccessor, OnDestroy, O } private reset() { - this.suggestedItems = []; - this.suggestedIndex = -1; + this.next(s => ({ + ...s, + suggestedItems: [], + suggestedIndex: -1 + })); } } \ 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 a426f93cc..d34486b19 100644 --- a/src/Squidex/app/framework/angular/forms/checkbox-group.component.ts +++ b/src/Squidex/app/framework/angular/forms/checkbox-group.component.ts @@ -44,7 +44,7 @@ export class CheckboxGroupComponent extends StatefulControlComponent this.values.indexOf(x) >= 0) : []; - this.next({ checkedValues }); + this.next(s => ({ ...s, checkedValues })); } public check(isChecked: boolean, value: string) { @@ -56,7 +56,7 @@ export class CheckboxGroupComponent extends StatefulControlComponent x !== value); } - this.next({ checkedValues }); + this.next(s => ({ ...s, checkedValues })); this.callChange(checkedValues); } 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 1e2215f0b..26f8a0ce7 100644 --- a/src/Squidex/app/framework/angular/forms/control-errors.component.ts +++ b/src/Squidex/app/framework/angular/forms/control-errors.component.ts @@ -127,6 +127,6 @@ export class ControlErrorsComponent extends StatefulComponent implements } } - this.next({ errorMessages }); + this.next(() => ({ errorMessages })); } } \ No newline at end of file diff --git a/src/Squidex/app/framework/angular/forms/stars.component.ts b/src/Squidex/app/framework/angular/forms/stars.component.ts index 75761a388..69a0e24ca 100644 --- a/src/Squidex/app/framework/angular/forms/stars.component.ts +++ b/src/Squidex/app/framework/angular/forms/stars.component.ts @@ -6,7 +6,7 @@ */ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, forwardRef, Input } from '@angular/core'; -import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { NG_VALUE_ACCESSOR } from '@angular/forms'; import { StatefulControlComponent, Types } from '@app/framework/internal'; @@ -28,7 +28,7 @@ interface State { providers: [SQX_STARS_CONTROL_VALUE_ACCESSOR], changeDetection: ChangeDetectionStrategy.OnPush }) -export class StarsComponent extends StatefulControlComponent implements ControlValueAccessor { +export class StarsComponent extends StatefulControlComponent { private maximumStarsValue = 5; @Input() @@ -38,13 +38,13 @@ export class StarsComponent extends StatefulControlComponent ({ ...s, starsArray })); } } @@ -63,7 +63,7 @@ export class StarsComponent extends StatefulControlComponent ({ ...s, stars: value, value })); } public setPreview(stars: number) { @@ -71,7 +71,7 @@ export class StarsComponent extends StatefulControlComponent ({ ...s, stars })); } public stopPreview() { @@ -79,7 +79,7 @@ export class StarsComponent extends StatefulControlComponent { s.stars = s.value || 0; }); + this.next(s => ({ ...s, stars: s.value || 0 })); } public reset() { @@ -88,7 +88,7 @@ export class StarsComponent extends StatefulControlComponent ({ ...s, stars: -1, value: null })); this.callChange(null); this.callTouched(); @@ -103,7 +103,7 @@ export class StarsComponent extends StatefulControlComponent ({ ...s, stars: value, value })); this.callChange(value); this.callTouched(); diff --git a/src/Squidex/app/framework/angular/forms/tag-editor.component.html b/src/Squidex/app/framework/angular/forms/tag-editor.component.html index 5aa03539b..36d93bf22 100644 --- a/src/Squidex/app/framework/angular/forms/tag-editor.component.html +++ b/src/Squidex/app/framework/angular/forms/tag-editor.component.html @@ -1,9 +1,9 @@
- + [class.focus]="snapshot.hasFocus" + [class.disabled]snapshot.="addInput.disabled"> + {{item}} @@ -23,13 +23,13 @@ spellcheck="false">
-
-
+
+ [sqxScrollActive]="i === snapshot.suggestedIndex"> {{item}}
diff --git a/src/Squidex/app/framework/angular/forms/tag-editor.component.ts b/src/Squidex/app/framework/angular/forms/tag-editor.component.ts index 32e0b96c4..8927195c7 100644 --- a/src/Squidex/app/framework/angular/forms/tag-editor.component.ts +++ b/src/Squidex/app/framework/angular/forms/tag-editor.component.ts @@ -6,7 +6,7 @@ */ import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, forwardRef, Input, OnInit, ViewChild } from '@angular/core'; -import { ControlValueAccessor, FormControl, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { FormControl, NG_VALUE_ACCESSOR } from '@angular/forms'; import { distinctUntilChanged, map, tap } from 'rxjs/operators'; import { StatefulControlComponent, Types } from '@app/framework/internal'; @@ -90,7 +90,7 @@ interface State { providers: [SQX_TAG_EDITOR_CONTROL_VALUE_ACCESSOR], changeDetection: ChangeDetectionStrategy.OnPush }) -export class TagEditorComponent extends StatefulControlComponent implements AfterViewInit, ControlValueAccessor, OnInit { +export class TagEditorComponent extends StatefulControlComponent implements AfterViewInit, OnInit { @Input() public converter: Converter = new StringConverter(); @@ -167,8 +167,11 @@ export class TagEditorComponent extends StatefulControlComponent i } })) .subscribe(items => { - this.suggestedIndex = -1; - this.suggestedItems = items || []; + this.next(s => ({ + ...s, + suggestedIndex: -1, + suggestedItems: items || [] + })); })); } @@ -177,12 +180,10 @@ export class TagEditorComponent extends StatefulControlComponent i this.resetSize(); if (this.converter && Types.isArrayOf(obj, v => this.converter.isValidValue(v))) { - this.items = obj; + this.next(s => ({ ...s, items: obj })); } else { - this.items = []; + this.next(s => ({ ...s, items: [] })); } - - this.changeDetector.markForCheck(); } public setDisabledState(isDisabled: boolean): void { @@ -197,7 +198,7 @@ export class TagEditorComponent extends StatefulControlComponent i public focus() { if (this.addInput.enabled) { - this.next({ hasFocus: true }); + this.next(s => ({ ...s, hasFocus: true })); } } @@ -211,7 +212,7 @@ export class TagEditorComponent extends StatefulControlComponent i } public remove(index: number) { - this.updateItems([...this.items.slice(0, index), ...this.items.splice(index + 1)]); + this.updateItems(this.snapshot.items.filter((_, i) => i !== index)); } public resetSize() { @@ -265,7 +266,7 @@ export class TagEditorComponent extends StatefulControlComponent i const value = this.addInput.value; if (!value || value.length === 0) { - this.updateItems(this.items.slice(0, this.items.length - 1)); + this.updateItems(this.snapshot.items.slice(0, this.snapshot.items.length - 1)); return false; } @@ -276,8 +277,8 @@ export class TagEditorComponent extends StatefulControlComponent i this.down(); return false; } else if (key === KEY_ENTER) { - if (this.suggestedIndex >= 0) { - if (this.selectValue(this.suggestedItems[this.suggestedIndex])) { + if (this.snapshot.suggestedIndex >= 0) { + if (this.selectValue(this.snapshot.suggestedItems[this.snapshot.suggestedIndex])) { return false; } } else if (this.acceptEnter) { @@ -298,8 +299,8 @@ export class TagEditorComponent extends StatefulControlComponent i if (value && this.converter.isValidInput(value)) { const converted = this.converter.convert(value); - if (this.allowDuplicates || this.items.indexOf(converted) < 0) { - this.updateItems([...this.items, converted]); + if (this.allowDuplicates || this.snapshot.items.indexOf(converted) < 0) { + this.updateItems([...this.snapshot.items, converted]); } this.resetForm(); @@ -309,24 +310,27 @@ export class TagEditorComponent extends StatefulControlComponent i } private resetAutocompletion() { - this.suggestedItems = []; - this.suggestedIndex = -1; + this.next(s => ({ + ...s, + suggestedItems: [], + suggestedIndex: -1 + })); } - public selectIndex(selection: number) { - if (selection < 0) { - selection = 0; + public selectIndex(suggestedIndex: number) { + if (suggestedIndex < 0) { + suggestedIndex = 0; } - if (selection >= this.suggestedItems.length) { - selection = this.suggestedItems.length - 1; + if (suggestedIndex >= this.snapshot.suggestedItems.length) { + suggestedIndex = this.snapshot.suggestedItems.length - 1; } - this.suggestedIndex = selection; + this.next(s => ({ ...s, suggestedIndex })); } public resetFocus(): any { - this.hasFocus = false; + this.next(s => ({ ...s, hasFocus: false })); } private resetForm() { @@ -334,11 +338,11 @@ export class TagEditorComponent extends StatefulControlComponent i } private up() { - this.selectIndex(this.suggestedIndex - 1); + this.selectIndex(this.snapshot.suggestedIndex - 1); } private down() { - this.selectIndex(this.suggestedIndex + 1); + this.selectIndex(this.snapshot.suggestedIndex + 1); } public onCut(event: ClipboardEvent) { @@ -351,7 +355,7 @@ export class TagEditorComponent extends StatefulControlComponent i public onCopy(event: ClipboardEvent) { if (!this.hasSelection()) { - event.clipboardData.setData('text/plain', this.items.filter(x => !!x).join(',')); + event.clipboardData.setData('text/plain', this.snapshot.items.filter(x => !!x).join(',')); event.preventDefault(); } @@ -363,7 +367,7 @@ export class TagEditorComponent extends StatefulControlComponent i if (value) { this.resetForm(); - const values = [...this.items]; + const values = [...this.snapshot.items]; for (let part of value.split(',')) { const converted = this.converter.convert(part); @@ -387,12 +391,12 @@ export class TagEditorComponent extends StatefulControlComponent i } private updateItems(items: any[]) { - const items = items; + this.next(s => ({ ...s, items })); if (items.length === 0 && this.undefinedWhenEmpty) { this.callChange(undefined); } else { - this.callChange(this.items); + this.callChange(items); } this.resetSize(); diff --git a/src/Squidex/app/framework/angular/forms/toggle.component.ts b/src/Squidex/app/framework/angular/forms/toggle.component.ts index 2fb6142dd..5eb23cb82 100644 --- a/src/Squidex/app/framework/angular/forms/toggle.component.ts +++ b/src/Squidex/app/framework/angular/forms/toggle.component.ts @@ -6,7 +6,7 @@ */ import { ChangeDetectorRef, Component, forwardRef, Input } from '@angular/core'; -import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { NG_VALUE_ACCESSOR } from '@angular/forms'; import { Types } from '@app/framework/internal'; @@ -26,7 +26,7 @@ interface State { templateUrl: './toggle.component.html', providers: [SQX_TOGGLE_CONTROL_VALUE_ACCESSOR] }) -export class ToggleComponent extends StatefulControlComponent implements ControlValueAccessor { +export class ToggleComponent extends StatefulControlComponent { @Input() public threeStates = false; @@ -37,7 +37,9 @@ export class ToggleComponent extends StatefulControlComponent ({ ...s, isChecked })); } public changeState(event: MouseEvent) { @@ -59,7 +61,7 @@ export class ToggleComponent extends StatefulControlComponent ({ ...s, isChecked })); this.callChange(isChecked); this.callTouched(); 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 335a19da0..666da8e15 100644 --- a/src/Squidex/app/framework/angular/modals/dialog-renderer.component.ts +++ b/src/Squidex/app/framework/angular/modals/dialog-renderer.component.ts @@ -55,9 +55,10 @@ export class DialogRendererComponent extends StatefulComponent implements this.observe( this.dialogs.notifications.subscribe(notification => { - this.next(state => { - state.notifications = [...state.notifications, notification]; - }); + this.next(s => ({ + ...s, + notifications: [...s.notifications, notification] + })); if (notification.displayTime > 0) { this.observe(timer(notification.displayTime).subscribe(() => { @@ -68,12 +69,10 @@ export class DialogRendererComponent extends StatefulComponent implements this.observe( this.dialogs.dialogs - .subscribe(request => { + .subscribe(dialogRequest => { this.cancel(); - this.next(state => { - state.dialogRequest = request; - }); + this.next(s => ({ ...s, dialogRequest })); })); } @@ -90,17 +89,16 @@ export class DialogRendererComponent extends StatefulComponent implements } private finishRequest(value: boolean) { - this.next(state => { - if (state.dialogRequest) { - state.dialogRequest.complete(value); - state.dialogRequest = null; + this.next(s => { + if (s.dialogRequest) { + s.dialogRequest.complete(value); } + + return { ...s, dialogRequest: null }; }); } public close(notification: Notification) { - this.next(state => { - state.notifications = state.notifications.filter(n => notification !== n); - }); + this.next(s => ({ ...s, notifications: s.notifications.filter(n => notification !== n) })); } } \ No newline at end of file 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 70b6c1cb3..b0de1483f 100644 --- a/src/Squidex/app/framework/angular/modals/onboarding-tooltip.component.ts +++ b/src/Squidex/app/framework/angular/modals/onboarding-tooltip.component.ts @@ -12,7 +12,7 @@ import { fadeAnimation, ModalModel, OnboardingService, - PureComponent, + StatefulComponent, Types } from '@app/framework/internal'; @@ -25,7 +25,7 @@ import { ], changeDetection: ChangeDetectionStrategy.OnPush }) -export class OnboardingTooltipComponent extends PureComponent implements OnDestroy, OnInit { +export class OnboardingTooltipComponent extends StatefulComponent implements OnDestroy, OnInit { public tooltipModal = new ModalModel(); @Input() @@ -44,7 +44,7 @@ export class OnboardingTooltipComponent extends PureComponent implements OnDestr private readonly onboardingService: OnboardingService, private readonly renderer: Renderer2 ) { - super(changeDetector); + super(changeDetector, {}); } public ngOnDestroy() { 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 2bd9862dd..2f3ec887c 100644 --- a/src/Squidex/app/framework/angular/modals/root-view.component.ts +++ b/src/Squidex/app/framework/angular/modals/root-view.component.ts @@ -5,9 +5,7 @@ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ -import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ViewChild, ViewContainerRef } from '@angular/core'; - -import { PureComponent } from '@app/framework/internal'; +import { ChangeDetectionStrategy, Component, ViewChild, ViewContainerRef } from '@angular/core'; @Component({ selector: 'sqx-root-view', @@ -15,11 +13,7 @@ import { PureComponent } from '@app/framework/internal'; templateUrl: './root-view.component.html', changeDetection: ChangeDetectionStrategy.OnPush }) -export class RootViewComponent extends PureComponent { +export class RootViewComponent { @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 8a0e558a8..d4c921544 100644 --- a/src/Squidex/app/framework/angular/modals/tooltip.component.ts +++ b/src/Squidex/app/framework/angular/modals/tooltip.component.ts @@ -10,7 +10,7 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy import { fadeAnimation, ModalModel, - PureComponent + StatefulComponent } from '@app/framework/internal'; @Component({ @@ -22,7 +22,7 @@ import { ], changeDetection: ChangeDetectionStrategy.OnPush }) -export class TooltipComponent extends PureComponent implements OnDestroy, OnInit { +export class TooltipComponent extends StatefulComponent implements OnDestroy, OnInit { @Input() public target: any; @@ -34,7 +34,7 @@ export class TooltipComponent extends PureComponent implements OnDestroy, OnInit constructor(changeDetector: ChangeDetectorRef, private readonly renderer: Renderer2 ) { - super(changeDetector); + super(changeDetector, {}); } public ngOnInit() { diff --git a/src/Squidex/app/framework/angular/pager.component.ts b/src/Squidex/app/framework/angular/pager.component.ts index dce6f8f8b..c85eaf9cd 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, ChangeDetectorRef, Component, EventEmitter, Input, Output } from '@angular/core'; +import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; -import { Pager, PureComponent } from '@app/framework/internal'; +import { Pager } from '@app/framework/internal'; @Component({ selector: 'sqx-pager', @@ -15,7 +15,7 @@ import { Pager, PureComponent } from '@app/framework/internal'; templateUrl: './pager.component.html', changeDetection: ChangeDetectionStrategy.OnPush }) -export class PagerComponent extends PureComponent { +export class PagerComponent { @Output() public nextPage = new EventEmitter(); @@ -27,8 +27,4 @@ export class PagerComponent extends PureComponent { @Input() public hideWhenButtonsDisabled = false; - - 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 68bdb01d4..e29d14327 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, ChangeDetectorRef, Component, ElementRef, Input, OnDestroy, OnInit, Renderer2, ViewChild } from '@angular/core'; +import { AfterViewInit, ChangeDetectionStrategy, Component, ElementRef, Input, OnDestroy, OnInit, Renderer2, ViewChild } from '@angular/core'; -import { PureComponent, slideRightAnimation } from '@app/framework/internal'; +import { 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 extends PureComponent implements AfterViewInit, OnDestroy, OnInit { +export class PanelComponent implements AfterViewInit, OnDestroy, OnInit { private styleWidth: string; public renderWidth = 0; @@ -61,11 +61,10 @@ export class PanelComponent extends PureComponent implements AfterViewInit, OnDe @ViewChild('panel') public panel: ElementRef; - constructor(changeDetector: ChangeDetectorRef, + constructor( private readonly container: PanelContainerDirective, private readonly renderer: Renderer2 ) { - super(changeDetector); } public ngOnDestroy() { diff --git a/src/Squidex/app/framework/angular/shortcut.component.ts b/src/Squidex/app/framework/angular/shortcut.component.ts index c264be77d..d1b2b993f 100644 --- a/src/Squidex/app/framework/angular/shortcut.component.ts +++ b/src/Squidex/app/framework/angular/shortcut.component.ts @@ -7,13 +7,13 @@ import { ChangeDetectorRef, Component, EventEmitter, Input, NgZone, OnDestroy, OnInit, Output } from '@angular/core'; -import { PureComponent, ShortcutService } from '@app/framework/internal'; +import { ShortcutService, StatefulComponent } from '@app/framework/internal'; @Component({ selector: 'sqx-shortcut', template: '' }) -export class ShortcutComponent extends PureComponent implements OnDestroy, OnInit { +export class ShortcutComponent extends StatefulComponent implements OnDestroy, OnInit { private lastKeys: string; @Input() @@ -30,7 +30,7 @@ export class ShortcutComponent extends PureComponent implements OnDestroy, OnIni private readonly shortcutService: ShortcutService, private readonly zone: NgZone ) { - super(changeDetector); + super(changeDetector, {}); changeDetector.detach(); } diff --git a/src/Squidex/app/framework/angular/stateful.component.ts b/src/Squidex/app/framework/angular/stateful.component.ts index 4ce595620..fad4ab6df 100644 --- a/src/Squidex/app/framework/angular/stateful.component.ts +++ b/src/Squidex/app/framework/angular/stateful.component.ts @@ -15,7 +15,7 @@ import { State } from '../state'; declare type UnsubscribeFunction = () => void; -export abstract class StatefulComponent extends State implements OnDestroy, OnInit { +export abstract class StatefulComponent extends State implements OnDestroy, OnInit { private subscriptions: (Subscription | UnsubscribeFunction)[] = []; constructor( @@ -52,11 +52,7 @@ export abstract class StatefulComponent extends State implements OnDestroy } } -export interface FormControlState { - isDisabled: boolean; -} - -export abstract class StatefulControlComponent extends StatefulComponent implements ControlValueAccessor { +export abstract class StatefulControlComponent extends StatefulComponent implements ControlValueAccessor { private fnChanged = (v: any) => { /* NOOP */ }; private fnTouched = () => { /* NOOP */ }; @@ -81,24 +77,18 @@ export abstract class StatefulControlComponent extends StatefulCompon } public setDisabledState(isDisabled: boolean): void { - this.next(state => { state.isDisabled = isDisabled; }); + this.next(s => ({ ...s, isDisabled })); } public abstract writeValue(obj: any): void; } -export abstract class PureComponent extends StatefulComponent { - constructor(changeDetector: ChangeDetectorRef) { - super(changeDetector, {}); - } -} - -export abstract class ExternalControlComponent extends PureComponent implements ControlValueAccessor { +export abstract class ExternalControlComponent extends StatefulComponent implements ControlValueAccessor { private fnChanged = (v: any) => { /* NOOP */ }; private fnTouched = () => { /* NOOP */ }; constructor(changeDetector: ChangeDetectorRef) { - super(changeDetector); + super(changeDetector, {}); changeDetector.detach(); } diff --git a/src/Squidex/app/framework/angular/user-report.component.ts b/src/Squidex/app/framework/angular/user-report.component.ts index ed197e543..9d2b32ba7 100644 --- a/src/Squidex/app/framework/angular/user-report.component.ts +++ b/src/Squidex/app/framework/angular/user-report.component.ts @@ -9,8 +9,8 @@ import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core'; import { timer } from 'rxjs'; import { - PureComponent, ResourceLoaderService, + StatefulComponent, UserReportConfig } from '@app/framework/internal'; @@ -18,12 +18,12 @@ import { selector: 'sqx-user-report', template: '' }) -export class UserReportComponent extends PureComponent implements OnDestroy, OnInit { +export class UserReportComponent extends StatefulComponent implements OnDestroy, OnInit { constructor(changeDetector: ChangeDetectorRef, private readonly config: UserReportConfig, private readonly resourceLoader: ResourceLoaderService ) { - super(changeDetector); + super(changeDetector, {}); changeDetector.detach(); } diff --git a/src/Squidex/app/framework/state.ts b/src/Squidex/app/framework/state.ts index d7690801d..357acb2ed 100644 --- a/src/Squidex/app/framework/state.ts +++ b/src/Squidex/app/framework/state.ts @@ -49,13 +49,13 @@ export class Form { } public load(value: any) { - this.state.next({ submitted: false, error: null }); + this.state.next(_ => ({ submitted: false, error: null })); this.setValue(value); } public submit(): any | null { - this.state.next({ submitted: true }); + this.state.next(_ => ({ submitted: true })); if (this.form.valid) { const value = fullValue(this.form); @@ -69,7 +69,7 @@ export class Form { } public submitCompleted(newValue?: any) { - this.state.next({ submitted: false, error: null }); + this.state.next(_ => ({ submitted: false, error: null })); this.enable(); @@ -81,7 +81,7 @@ export class Form { } public submitFailed(error?: string | ErrorDto) { - this.state.next({ submitted: false, error: this.getError(error) }); + this.state.next(_ => ({ submitted: false, error: this.getError(error) })); this.enable(); } @@ -133,17 +133,10 @@ export class State { } public resetState() { - this.next(this.initialState); + this.next(_ => this.initialState); } - public next(update: ((v: T) => T | void) | Partial) { - if (Types.isFunction(update)) { - const stateNew = { ...this.snapshot }; - const stateUpdated = update(stateNew); - - this.state.next(stateUpdated || stateNew); - } else { - this.state.next({ ...this.snapshot, ...update }); - } + public next(update: (v: T) => T) { + this.state.next(update(this.snapshot)); } } \ No newline at end of file diff --git a/src/Squidex/app/shared/components/geolocation-editor.component.html b/src/Squidex/app/shared/components/geolocation-editor.component.html index fdf03b724..768fe3b9e 100644 --- a/src/Squidex/app/shared/components/geolocation-editor.component.html +++ b/src/Squidex/app/shared/components/geolocation-editor.component.html @@ -1,7 +1,7 @@
- +
@@ -24,7 +24,7 @@
- +
diff --git a/src/Squidex/app/shared/components/geolocation-editor.component.ts b/src/Squidex/app/shared/components/geolocation-editor.component.ts index 8d1ec494a..00de85805 100644 --- a/src/Squidex/app/shared/components/geolocation-editor.component.ts +++ b/src/Squidex/app/shared/components/geolocation-editor.component.ts @@ -6,10 +6,11 @@ */ import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, forwardRef, ViewChild } from '@angular/core'; -import { ControlValueAccessor, FormBuilder, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { FormBuilder, NG_VALUE_ACCESSOR } from '@angular/forms'; import { ResourceLoaderService, + StatefulControlComponent, Types, UIState, ValidatorsEx @@ -27,6 +28,10 @@ interface Geolocation { longitude: number; } +interface State { + isGoogleMaps: boolean; +} + @Component({ selector: 'sqx-geolocation-editor', styleUrls: ['./geolocation-editor.component.scss'], @@ -34,9 +39,7 @@ interface Geolocation { providers: [SQX_GEOLOCATION_EDITOR_CONTROL_VALUE_ACCESSOR], changeDetection: ChangeDetectionStrategy.OnPush }) -export class GeolocationEditorComponent implements ControlValueAccessor, AfterViewInit { - private callChange = (v: any) => { /* NOOP */ }; - private callTouched = () => { /* NOOP */ }; +export class GeolocationEditorComponent extends StatefulControlComponent implements AfterViewInit { private marker: any; private map: any; private value: Geolocation | null = null; @@ -62,20 +65,19 @@ export class GeolocationEditorComponent implements ControlValueAccessor, AfterVi }); @ViewChild('editor') - public editor: ElementRef; + public editor: ElementRef; @ViewChild('searchBox') - public searchBoxInput: ElementRef; - - public isGoogleMaps = false; - public isDisabled = false; + public searchBoxInput: ElementRef; - constructor( - private readonly changeDetector: ChangeDetectorRef, + constructor(changeDetector: ChangeDetectorRef, private readonly resourceLoader: ResourceLoaderService, private readonly formBuilder: FormBuilder, private readonly uiState: UIState ) { + super(changeDetector, { + isGoogleMaps: false + }); } public writeValue(obj: any) { @@ -91,9 +93,9 @@ export class GeolocationEditorComponent implements ControlValueAccessor, AfterVi } public setDisabledState(isDisabled: boolean): void { - this.isDisabled = isDisabled; + super.setDisabledState(isDisabled); - if (!this.isGoogleMaps) { + if (!this.snapshot.isGoogleMaps) { this.setDisabledStateOSM(isDisabled); } else { this.setDisabledStateGoogle(isDisabled); @@ -104,8 +106,6 @@ export class GeolocationEditorComponent implements ControlValueAccessor, AfterVi } else { this.geolocationForm.enable(); } - - this.changeDetector.markForCheck(); } private setDisabledStateOSM(isDisabled: boolean): void { @@ -139,14 +139,6 @@ export class GeolocationEditorComponent implements ControlValueAccessor, AfterVi } } - public registerOnChange(fn: any) { - this.callChange = fn; - } - - public registerOnTouched(fn: any) { - this.callTouched = fn; - } - public updateValueByInput() { const lat = this.geolocationForm.controls['latitude'].value; const lng = this.geolocationForm.controls['longitude'].value; @@ -164,9 +156,11 @@ export class GeolocationEditorComponent implements ControlValueAccessor, AfterVi public ngAfterViewInit() { this.uiState.settings .subscribe(settings => { - this.isGoogleMaps = settings.mapType === 'GoogleMaps'; + const isGoogleMaps = settings.mapType === 'GoogleMaps'; + + this.next(s => ({ ...s, isGoogleMaps })); - if (!this.isGoogleMaps) { + if (!this.snapshot.isGoogleMaps) { this.ngAfterViewInitOSM(); } else { this.ngAfterViewInitGoogle(settings.mapKey); @@ -189,7 +183,7 @@ export class GeolocationEditorComponent implements ControlValueAccessor, AfterVi this.map.on('click', (event: any) => { - if (!this.marker && !this.isDisabled) { + if (!this.marker && !this.snapshot.isDisabled) { const latlng = event.latlng.wrap(); this.updateValue(latlng.lat, latlng.lng); @@ -199,7 +193,7 @@ export class GeolocationEditorComponent implements ControlValueAccessor, AfterVi this.updateMarker(true, false); - if (this.isDisabled) { + if (this.snapshot.isDisabled) { this.map.zoomControl.disable(); this.map._handlers.forEach((handler: any) => { @@ -224,7 +218,7 @@ export class GeolocationEditorComponent implements ControlValueAccessor, AfterVi this.map.addListener('click', (event: any) => { - if (!this.isDisabled) { + if (!this.snapshot.isDisabled) { this.updateValue(event.latLng.lat(), event.latLng.lng()); this.updateMarker(false, true); } @@ -244,7 +238,7 @@ export class GeolocationEditorComponent implements ControlValueAccessor, AfterVi return; } - if (!this.isDisabled) { + if (!this.snapshot.isDisabled) { let lat = place.geometry.location.lat(); let lng = place.geometry.location.lng(); @@ -256,7 +250,7 @@ export class GeolocationEditorComponent implements ControlValueAccessor, AfterVi this.updateMarker(true, false); - if (this.isDisabled) { + if (this.snapshot.isDisabled) { this.map.setOptions({ draggable: false, zoomControl: false }); } }); @@ -264,7 +258,7 @@ export class GeolocationEditorComponent implements ControlValueAccessor, AfterVi public reset() { this.value = null; - this.searchBoxInput.nativeElement.value = null; + this.searchBoxInput.nativeElement.value = ''; this.updateMarker(true, true); } @@ -274,7 +268,7 @@ export class GeolocationEditorComponent implements ControlValueAccessor, AfterVi } private updateMarker(zoom: boolean, fireEvent: boolean) { - if (!this.isGoogleMaps) { + if (!this.snapshot.isGoogleMaps) { this.updateMarkerOSM(zoom); } else { this.updateMarkerGoogle(zoom); @@ -307,7 +301,7 @@ export class GeolocationEditorComponent implements ControlValueAccessor, AfterVi this.updateMarker(false, true); }); - if (this.isDisabled) { + if (this.snapshot.isDisabled) { this.marker.dragging.disable(); } } @@ -344,12 +338,12 @@ export class GeolocationEditorComponent implements ControlValueAccessor, AfterVi }); this.marker.addListener('drag', (event: any) => { - if (!this.isDisabled) { + if (!this.snapshot.isDisabled) { this.updateValue(event.latLng.lat(), event.LatLng.lng()); } }); this.marker.addListener('dragend', (event: any) => { - if (!this.isDisabled) { + if (!this.snapshot.isDisabled) { this.updateValue(event.latLng.lat(), event.LatLng.lng()); this.updateMarker(false, true); } diff --git a/src/Squidex/app/shared/components/markdown-editor.component.html b/src/Squidex/app/shared/components/markdown-editor.component.html index 7480c92da..79380fdc6 100644 --- a/src/Squidex/app/shared/components/markdown-editor.component.html +++ b/src/Squidex/app/shared/components/markdown-editor.component.html @@ -1,5 +1,5 @@
-
+
diff --git a/src/Squidex/app/shared/components/markdown-editor.component.ts b/src/Squidex/app/shared/components/markdown-editor.component.ts index 3d673176a..4b1b23514 100644 --- a/src/Squidex/app/shared/components/markdown-editor.component.ts +++ b/src/Squidex/app/shared/components/markdown-editor.component.ts @@ -6,12 +6,13 @@ */ import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, forwardRef, Renderer2, ViewChild } from '@angular/core'; -import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { NG_VALUE_ACCESSOR } from '@angular/forms'; import { AssetDto, DialogModel, ResourceLoaderService, + StatefulControlComponent, Types } from '@app/shared/internal'; @@ -21,6 +22,10 @@ export const SQX_MARKDOWN_EDITOR_CONTROL_VALUE_ACCESSOR: any = { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => MarkdownEditorComponent), multi: true }; +interface State { + isFullscreen: false; +} + @Component({ selector: 'sqx-markdown-editor', styleUrls: ['./markdown-editor.component.scss'], @@ -28,9 +33,7 @@ export const SQX_MARKDOWN_EDITOR_CONTROL_VALUE_ACCESSOR: any = { providers: [SQX_MARKDOWN_EDITOR_CONTROL_VALUE_ACCESSOR], changeDetection: ChangeDetectionStrategy.OnPush }) -export class MarkdownEditorComponent implements ControlValueAccessor, AfterViewInit { - private callChange = (v: any) => { /* NOOP */ }; - private callTouched = () => { /* NOOP */ }; +export class MarkdownEditorComponent extends StatefulControlComponent implements AfterViewInit { private simplemde: any; private value: string; private isDisabled = false; @@ -46,13 +49,14 @@ export class MarkdownEditorComponent implements ControlValueAccessor, AfterViewI @ViewChild('inner') public inner: ElementRef; - public isFullscreen = false; - - constructor( - private readonly changeDetector: ChangeDetectorRef, + constructor(changeDetector: ChangeDetectorRef, private readonly renderer: Renderer2, private readonly resourceLoader: ResourceLoaderService ) { + super(changeDetector, { + isFullscreen: false + }); + this.resourceLoader.loadStyle('https://cdn.jsdelivr.net/simplemde/latest/simplemde.min.css'); } @@ -72,18 +76,8 @@ export class MarkdownEditorComponent implements ControlValueAccessor, AfterViewI } } - public registerOnChange(fn: any) { - this.callChange = fn; - } - - public registerOnTouched(fn: any) { - this.callTouched = fn; - } - private showSelector = () => { this.assetsDialog.show(); - - this.changeDetector.detectChanges(); } public ngAfterViewInit() { @@ -182,17 +176,17 @@ export class MarkdownEditorComponent implements ControlValueAccessor, AfterViewI }); this.simplemde.codemirror.on('refresh', () => { - this.isFullscreen = this.simplemde.isFullscreenActive(); + const isFullscreen = this.simplemde.isFullscreenActive(); let target = this.container.nativeElement; - if (this.isFullscreen) { + if (isFullscreen) { target = document.body; } this.renderer.appendChild(target, this.inner.nativeElement); - this.changeDetector.detectChanges(); + this.next(s => ({ ...s, isFullscreen })); }); this.simplemde.codemirror.on('blur', () => { diff --git a/src/Squidex/app/shared/components/rich-editor.component.ts b/src/Squidex/app/shared/components/rich-editor.component.ts index 5eee435e5..8abe54b0e 100644 --- a/src/Squidex/app/shared/components/rich-editor.component.ts +++ b/src/Squidex/app/shared/components/rich-editor.component.ts @@ -6,11 +6,12 @@ */ import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, EventEmitter, forwardRef, OnDestroy, Output, ViewChild } from '@angular/core'; -import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { NG_VALUE_ACCESSOR } from '@angular/forms'; import { AssetDto, DialogModel, + ExternalControlComponent, ResourceLoaderService, Types } from '@app/shared/internal'; @@ -28,9 +29,7 @@ export const SQX_RICH_EDITOR_CONTROL_VALUE_ACCESSOR: any = { providers: [SQX_RICH_EDITOR_CONTROL_VALUE_ACCESSOR], changeDetection: ChangeDetectionStrategy.OnPush }) -export class RichEditorComponent implements ControlValueAccessor, AfterViewInit, OnDestroy { - private callChange = (v: any) => { /* NOOP */ }; - private callTouched = () => { /* NOOP */ }; +export class RichEditorComponent extends ExternalControlComponent implements AfterViewInit, OnDestroy { private tinyEditor: any; private tinyInitTimer: any; private value: string; @@ -44,10 +43,10 @@ export class RichEditorComponent implements ControlValueAccessor, AfterViewInit, @Output() public assetPluginClicked = new EventEmitter(); - constructor( - private readonly changeDetector: ChangeDetectorRef, + constructor(changeDetector: ChangeDetectorRef, private readonly resourceLoader: ResourceLoaderService ) { + super(changeDetector); } public ngOnDestroy() { @@ -66,8 +65,6 @@ export class RichEditorComponent implements ControlValueAccessor, AfterViewInit, private showSelector = () => { this.assetsDialog.show(); - - this.changeDetector.detectChanges(); } private getEditorOptions() { @@ -132,14 +129,6 @@ export class RichEditorComponent implements ControlValueAccessor, AfterViewInit, } } - public registerOnChange(fn: any) { - this.callChange = fn; - } - - public registerOnTouched(fn: any) { - this.callTouched = fn; - } - public insertAssets(assets: AssetDto[]) { let content = ''; diff --git a/src/Squidex/app/shared/state/ui.state.ts b/src/Squidex/app/shared/state/ui.state.ts index 84a633a81..f71f500c2 100644 --- a/src/Squidex/app/shared/state/ui.state.ts +++ b/src/Squidex/app/shared/state/ui.state.ts @@ -53,7 +53,7 @@ export class UIState extends State { this.uiService.getSettings(this.appName) .subscribe(dtos => { - return this.next({ settings: dtos }); + return this.next(s => ({ ...s, settings: dtos })); }); } @@ -65,7 +65,7 @@ export class UIState extends State { current[key] = value; - this.next({ settings: root }); + this.next(s => ({ ...s, settings: root })); } } @@ -77,7 +77,7 @@ export class UIState extends State { delete current[key]; - this.next({ settings: root }); + this.next(s => ({ ...s, settings: root })); } }