From f95c0cc722ff1f8c89f9ec4d1852039f7a58108d Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Tue, 9 Oct 2018 17:50:32 +0200 Subject: [PATCH] Performance improvements. --- .../content/content-field.component.html | 2 +- .../pages/content/content-field.component.ts | 13 +- .../content/content-history.component.html | 5 +- .../content/content-history.component.ts | 4 + .../contents/contents-page.component.html | 8 +- .../pages/contents/contents-page.component.ts | 14 +- .../content/shared/assets-editor.component.ts | 2 +- .../content/shared/content-item.component.ts | 5 +- .../shared/references-editor.component.ts | 10 +- .../forms/date-time-editor.component.ts | 2 +- .../angular/http/loading.interceptor.ts | 31 + .../modals/dialog-renderer.component.ts | 20 +- .../modals/onboarding-tooltip.component.ts | 5 +- .../angular/modals/root-view.component.ts | 5 +- .../app/framework/angular/panel.component.ts | 9 +- .../framework/angular/shortcut.component.ts | 5 +- .../app/framework/angular/title.component.ts | 5 +- .../angular/user-report.component.ts | 5 +- src/Squidex/app/framework/declarations.ts | 1 + src/Squidex/app/framework/internal.ts | 1 + src/Squidex/app/framework/module.ts | 11 +- .../services/loading.service.spec.ts | 114 + .../app/framework/services/loading.service.ts | 68 + .../shared/components/asset.component.html | 4 +- .../shared/components/asset.component.scss | 8 + .../app/shared/components/asset.component.ts | 4 +- .../geolocation-editor.component.ts | 37 +- .../app/shared/components/help.component.ts | 3 +- .../components/history-list.component.html | 2 +- src/Squidex/app/shared/components/pipes.ts | 2 +- .../internal/internal-area.component.html | 8 +- .../internal/internal-area.component.scss | 4 + .../pages/internal/internal-area.component.ts | 8 +- src/Squidex/app/theme/_common.scss | 14 + .../app/theme/icomoon/demo-files/demo.css | 4 +- src/Squidex/app/theme/icomoon/demo.html | 776 +++-- .../app/theme/icomoon/fonts/icomoon.eot | Bin 25328 -> 25540 bytes .../app/theme/icomoon/fonts/icomoon.svg | 1 + .../app/theme/icomoon/fonts/icomoon.ttf | Bin 25164 -> 25376 bytes .../app/theme/icomoon/fonts/icomoon.woff | Bin 25240 -> 25452 bytes src/Squidex/app/theme/icomoon/selection.json | 3056 +---------------- src/Squidex/app/theme/icomoon/style.css | 124 +- src/Squidex/wwwroot/images/loader-white.gif | Bin 0 -> 33782 bytes 43 files changed, 838 insertions(+), 3562 deletions(-) create mode 100644 src/Squidex/app/framework/angular/http/loading.interceptor.ts create mode 100644 src/Squidex/app/framework/services/loading.service.spec.ts create mode 100644 src/Squidex/app/framework/services/loading.service.ts create mode 100644 src/Squidex/wwwroot/images/loader-white.gif diff --git a/src/Squidex/app/features/content/pages/content/content-field.component.html b/src/Squidex/app/features/content/pages/content/content-field.component.html index 3b7f60e12..b4ea1e648 100644 --- a/src/Squidex/app/features/content/pages/content/content-field.component.html +++ b/src/Squidex/app/features/content/pages/content/content-field.component.html @@ -1,4 +1,4 @@ -
+
; + public ngOnChanges(changes: SimpleChanges) { if (this.field.isLocalizable) { this.selectedFormControl = this.fieldForm.controls[this.language.iso2Code]; @@ -55,6 +60,10 @@ export class ContentFieldComponent implements OnChanges { this.selectedFormControl['_clearChangeFns'](); } } + + if (changes['fieldForm']) { + this.isInvalid = this.fieldForm.statusChanges.pipe(startWith(this.fieldForm.invalid), map(x => this.fieldForm.invalid)); + } } } diff --git a/src/Squidex/app/features/content/pages/content/content-history.component.html b/src/Squidex/app/features/content/pages/content/content-history.component.html index f9026f3dc..d2847fc45 100644 --- a/src/Squidex/app/features/content/pages/content/content-history.component.html +++ b/src/Squidex/app/features/content/pages/content/content-history.component.html @@ -4,13 +4,14 @@ -
+
- {{event.actor | sqxUserNameRef:null}} + {{event.actor | sqxUserNameRef:null}} +
{{event.created | sqxFromNow}}
diff --git a/src/Squidex/app/features/content/pages/content/content-history.component.ts b/src/Squidex/app/features/content/pages/content/content-history.component.ts index 09763dc08..087690153 100644 --- a/src/Squidex/app/features/content/pages/content/content-history.component.ts +++ b/src/Squidex/app/features/content/pages/content/content-history.component.ts @@ -71,4 +71,8 @@ export class ContentHistoryComponent { public format(message: string): Observable { return formatHistoryMessage(message, this.users); } + + public trackByEvent(index: number, event: HistoryEventDto) { + return event.eventId; + } } \ No newline at end of file diff --git a/src/Squidex/app/features/content/pages/contents/contents-page.component.html b/src/Squidex/app/features/content/pages/contents/contents-page.component.html index 0434c823e..74a81a361 100644 --- a/src/Squidex/app/features/content/pages/contents/contents-page.component.html +++ b/src/Squidex/app/features/content/pages/contents/contents-page.component.html @@ -73,19 +73,19 @@
{{selectionCount}} items selected:   - - - - diff --git a/src/Squidex/app/features/content/pages/contents/contents-page.component.ts b/src/Squidex/app/features/content/pages/contents/contents-page.component.ts index b3a3acbd4..be82c3a40 100644 --- a/src/Squidex/app/features/content/pages/contents/contents-page.component.ts +++ b/src/Squidex/app/features/content/pages/contents/contents-page.component.ts @@ -149,7 +149,7 @@ export class ContentsPageComponent implements OnDestroy, OnInit { } public isSelectedQuery(query: string) { - return query === this.contentsState.snapshot.contentsQuery || (!query && !this.contentsState.contentsQuery); + return query === this.contentsState.snapshot.contentsQuery || (!query && !this.contentsState.snapshot.contentsQuery); } private changeContentItems(contents: ContentDto[], action: string) { @@ -208,6 +208,12 @@ export class ContentsPageComponent implements OnDestroy, OnInit { this.updateSelectionSummary(); } + private resetSelection() { + this.selectedItems = {}; + + this.updateSelectionSummary(); + } + public selectAll(isSelected: boolean) { this.selectedItems = {}; @@ -224,12 +230,6 @@ export class ContentsPageComponent implements OnDestroy, OnInit { return content.id; } - private resetSelection() { - this.selectedItems = {}; - - this.updateSelectionSummary(); - } - private updateSelectionSummary() { this.isAllSelected = this.contentsState.snapshot.contents.length > 0; 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 4fdc69ade..b2ff9fa2f 100644 --- a/src/Squidex/app/features/content/shared/assets-editor.component.ts +++ b/src/Squidex/app/features/content/shared/assets-editor.component.ts @@ -132,7 +132,7 @@ export class AssetsEditorComponent implements ControlValueAccessor { this.callTouched(); this.callChange(ids); - this.changeDetector.markForCheck(); + this.changeDetector.detectChanges(); } public sort(assets: AssetDto[]) { diff --git a/src/Squidex/app/features/content/shared/content-item.component.ts b/src/Squidex/app/features/content/shared/content-item.component.ts index 567915cd4..697f9044e 100644 --- a/src/Squidex/app/features/content/shared/content-item.component.ts +++ b/src/Squidex/app/features/content/shared/content-item.component.ts @@ -5,7 +5,7 @@ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ -import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core'; +import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core'; import { AppLanguageDto, @@ -29,7 +29,8 @@ import { templateUrl: './content-item.component.html', animations: [ fadeAnimation - ] + ], + changeDetection: ChangeDetectionStrategy.OnPush }) export class ContentItemComponent implements OnChanges { @Output() 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 855594365..f0defad85 100644 --- a/src/Squidex/app/features/content/shared/references-editor.component.ts +++ b/src/Squidex/app/features/content/shared/references-editor.component.ts @@ -74,11 +74,11 @@ export class ReferencesEditorComponent implements ControlValueAccessor, OnInit { .subscribe(dto => { this.schema = dto; - this.changeDetector.markForCheck(); + this.changeDetector.detectChanges(); }, () => { this.isInvalidSchema = true; - this.changeDetector.markForCheck(); + this.changeDetector.detectChanges(); }); } @@ -95,11 +95,11 @@ export class ReferencesEditorComponent implements ControlValueAccessor, OnInit { this.updateValue(); } - this.changeDetector.markForCheck(); + this.changeDetector.detectChanges(); }, () => { this.contentItems = ImmutableArray.empty(); - this.changeDetector.markForCheck(); + this.changeDetector.detectChanges(); }); } } else { @@ -157,6 +157,6 @@ export class ReferencesEditorComponent implements ControlValueAccessor, OnInit { this.callTouched(); this.callChange(ids); - this.changeDetector.markForCheck(); + this.changeDetector.detectChanges(); } } \ No newline at end of file diff --git a/src/Squidex/app/framework/angular/forms/date-time-editor.component.ts b/src/Squidex/app/framework/angular/forms/date-time-editor.component.ts index 0021ed2c8..a98d02f29 100644 --- a/src/Squidex/app/framework/angular/forms/date-time-editor.component.ts +++ b/src/Squidex/app/framework/angular/forms/date-time-editor.component.ts @@ -144,7 +144,7 @@ export class DateTimeEditorComponent implements ControlValueAccessor, OnDestroy, this.touched(); if (false) { - this.changeDetector.markForCheck(); + this.changeDetector.detectChanges(); } } }); diff --git a/src/Squidex/app/framework/angular/http/loading.interceptor.ts b/src/Squidex/app/framework/angular/http/loading.interceptor.ts new file mode 100644 index 000000000..af6611f67 --- /dev/null +++ b/src/Squidex/app/framework/angular/http/loading.interceptor.ts @@ -0,0 +1,31 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. + */ + +import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http'; +import { Injectable} from '@angular/core'; +import { Observable } from 'rxjs'; +import { finalize } from 'rxjs/operators'; + +import { LoadingService, MathHelper } from './../../internal'; + +@Injectable() +export class LoadingInterceptor implements HttpInterceptor { + constructor( + private readonly loadingService: LoadingService + ) { + } + + public intercept(req: HttpRequest, next: HttpHandler): Observable> { + const id = MathHelper.guid(); + + this.loadingService.startLoading(id); + + return next.handle(req).pipe(finalize(() => { + this.loadingService.completeLoading(id); + })); + } +} \ No newline at end of file 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 2dabc425a..17c184f0c 100644 --- a/src/Squidex/app/framework/angular/modals/dialog-renderer.component.ts +++ b/src/Squidex/app/framework/angular/modals/dialog-renderer.component.ts @@ -5,7 +5,7 @@ * Copyright (c) Sebastian Stehle. All rights r vbeserved */ -import { Component, Input, OnDestroy, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy, OnInit } from '@angular/core'; import { Subscription } from 'rxjs'; import { @@ -22,7 +22,8 @@ import { templateUrl: './dialog-renderer.component.html', animations: [ fadeAnimation - ] + ], + changeDetection: ChangeDetectionStrategy.OnPush }) export class DialogRendererComponent implements OnDestroy, OnInit { private dialogSubscription: Subscription; @@ -38,6 +39,7 @@ export class DialogRendererComponent implements OnDestroy, OnInit { public position = 'bottomright'; constructor( + private readonly changeDetector: ChangeDetectorRef, private readonly dialogs: DialogService ) { } @@ -53,6 +55,8 @@ export class DialogRendererComponent implements OnDestroy, OnInit { this.dialogView.isOpen.subscribe(isOpen => { if (!isOpen) { this.cancel(); + + this.changeDetector.detectChanges(); } }); @@ -65,6 +69,8 @@ export class DialogRendererComponent implements OnDestroy, OnInit { this.close(notification); }, notification.displayTime); } + + this.changeDetector.detectChanges(); }); this.dialogsSubscription = @@ -74,6 +80,8 @@ export class DialogRendererComponent implements OnDestroy, OnInit { this.dialogRequest = request; this.dialogView.show(); + + this.changeDetector.detectChanges(); }); } @@ -94,6 +102,12 @@ export class DialogRendererComponent implements OnDestroy, OnInit { } public close(notification: Notification) { - this.notifications.splice(this.notifications.indexOf(notification), 1); + const index = this.notifications.indexOf(notification); + + if (index >= 0) { + this.notifications.splice(index, 1); + + this.changeDetector.detectChanges(); + } } } \ 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 b6cee0c64..a0cf8ab7a 100644 --- a/src/Squidex/app/framework/angular/modals/onboarding-tooltip.component.ts +++ b/src/Squidex/app/framework/angular/modals/onboarding-tooltip.component.ts @@ -5,7 +5,7 @@ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ -import { Component, Input, OnDestroy, OnInit, Renderer2 } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Input, OnDestroy, OnInit, Renderer2 } from '@angular/core'; import { fadeAnimation, @@ -20,7 +20,8 @@ import { templateUrl: './onboarding-tooltip.component.html', animations: [ fadeAnimation - ] + ], + changeDetection: ChangeDetectionStrategy.OnPush }) export class OnboardingTooltipComponent implements OnDestroy, OnInit { private showTimer: any; 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 4017109d9..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,12 +5,13 @@ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ -import { Component, ViewChild, ViewContainerRef } from '@angular/core'; +import { ChangeDetectionStrategy, Component, ViewChild, ViewContainerRef } from '@angular/core'; @Component({ selector: 'sqx-root-view', styleUrls: ['./root-view.component.scss'], - templateUrl: './root-view.component.html' + templateUrl: './root-view.component.html', + changeDetection: ChangeDetectionStrategy.OnPush }) export class RootViewComponent { @ViewChild('element', { read: ViewContainerRef }) diff --git a/src/Squidex/app/framework/angular/panel.component.ts b/src/Squidex/app/framework/angular/panel.component.ts index 7c70ef8fc..ed7ae21c2 100644 --- a/src/Squidex/app/framework/angular/panel.component.ts +++ b/src/Squidex/app/framework/angular/panel.component.ts @@ -5,7 +5,7 @@ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ -import { AfterViewInit, 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'; @@ -17,7 +17,8 @@ import { PanelContainerDirective } from './panel-container.directive'; templateUrl: './panel.component.html', animations: [ slideRightAnimation - ] + ], + changeDetection: ChangeDetectionStrategy.OnPush }) export class PanelComponent implements AfterViewInit, OnDestroy, OnInit { private styleWidth: string; @@ -62,6 +63,7 @@ export class PanelComponent implements AfterViewInit, OnDestroy, OnInit { constructor( private readonly container: PanelContainerDirective, + private readonly changeDetector: ChangeDetectorRef, private readonly renderer: Renderer2 ) { } @@ -83,8 +85,9 @@ export class PanelComponent implements AfterViewInit, OnDestroy, OnInit { this.styleWidth = size; this.renderer.setStyle(this.panel.nativeElement, 'width', size); - this.renderWidth = this.panel.nativeElement.getBoundingClientRect().width; + + this.changeDetector.detectChanges(); } } diff --git a/src/Squidex/app/framework/angular/shortcut.component.ts b/src/Squidex/app/framework/angular/shortcut.component.ts index 01148f3fb..26191b2eb 100644 --- a/src/Squidex/app/framework/angular/shortcut.component.ts +++ b/src/Squidex/app/framework/angular/shortcut.component.ts @@ -5,13 +5,14 @@ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ -import { Component, EventEmitter, Input, NgZone, OnDestroy, OnInit, Output } from '@angular/core'; +import { ChangeDetectionStrategy, Component, EventEmitter, Input, NgZone, OnDestroy, OnInit, Output } from '@angular/core'; import { ShortcutService } from './../services/shortcut.service'; @Component({ selector: 'sqx-shortcut', - template: '' + template: '', + changeDetection: ChangeDetectionStrategy.OnPush }) export class ShortcutComponent implements OnDestroy, OnInit { @Input() diff --git a/src/Squidex/app/framework/angular/title.component.ts b/src/Squidex/app/framework/angular/title.component.ts index 636e14a7b..20161b09a 100644 --- a/src/Squidex/app/framework/angular/title.component.ts +++ b/src/Squidex/app/framework/angular/title.component.ts @@ -5,13 +5,14 @@ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ -import { Component, Input, OnChanges } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Input, OnChanges } from '@angular/core'; import { TitleService } from './../services/title.service'; @Component({ selector: 'sqx-title', - template: '' + template: '', + changeDetection: ChangeDetectionStrategy.OnPush }) export class TitleComponent implements OnChanges { @Input() diff --git a/src/Squidex/app/framework/angular/user-report.component.ts b/src/Squidex/app/framework/angular/user-report.component.ts index 497a4b56f..0cb33edb7 100644 --- a/src/Squidex/app/framework/angular/user-report.component.ts +++ b/src/Squidex/app/framework/angular/user-report.component.ts @@ -5,14 +5,15 @@ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ -import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core'; import { UserReportConfig } from './../configurations'; import { ResourceLoaderService } from './../services/resource-loader.service'; @Component({ selector: 'sqx-user-report', - template: '' + template: '', + changeDetection: ChangeDetectionStrategy.OnPush }) export class UserReportComponent implements OnDestroy, OnInit { private loadingTimer: any; diff --git a/src/Squidex/app/framework/declarations.ts b/src/Squidex/app/framework/declarations.ts index dda4dbf42..16cc7f7dd 100644 --- a/src/Squidex/app/framework/declarations.ts +++ b/src/Squidex/app/framework/declarations.ts @@ -27,6 +27,7 @@ export * from './angular/forms/toggle.component'; export * from './angular/forms/transform-input.directive'; export * from './angular/forms/validators'; +export * from './angular/http/loading.interceptor'; export * from './angular/http/http-extensions'; export * from './angular/modals/dialog-renderer.component'; diff --git a/src/Squidex/app/framework/internal.ts b/src/Squidex/app/framework/internal.ts index b3771aa42..299635a8b 100644 --- a/src/Squidex/app/framework/internal.ts +++ b/src/Squidex/app/framework/internal.ts @@ -11,6 +11,7 @@ export * from './angular/animations'; export * from './services/analytics.service'; export * from './services/clipboard.service'; export * from './services/dialog.service'; +export * from './services/loading.service'; export * from './services/local-store.service'; export * from './services/message-bus.service'; export * from './services/onboarding.service'; diff --git a/src/Squidex/app/framework/module.ts b/src/Squidex/app/framework/module.ts index 210f86a97..b5b802278 100644 --- a/src/Squidex/app/framework/module.ts +++ b/src/Squidex/app/framework/module.ts @@ -6,6 +6,7 @@ */ import { CommonModule } from '@angular/common'; +import { HTTP_INTERCEPTORS } from '@angular/common/http'; import { ModuleWithProviders, NgModule } from '@angular/core'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; @@ -44,6 +45,8 @@ import { KeysPipe, KNumberPipe, LightenPipe, + LoadingInterceptor, + LoadingService, LocalStoreService, MessageBus, ModalDialogComponent, @@ -220,11 +223,17 @@ export class SqxFrameworkModule { ClipboardService, DialogService, LocalStoreService, + LoadingService, MessageBus, OnboardingService, ResourceLoaderService, ShortcutService, - TitleService + TitleService, + { + provide: HTTP_INTERCEPTORS, + useClass: LoadingInterceptor, + multi: true + } ] }; } diff --git a/src/Squidex/app/framework/services/loading.service.spec.ts b/src/Squidex/app/framework/services/loading.service.spec.ts new file mode 100644 index 000000000..2e7b8128d --- /dev/null +++ b/src/Squidex/app/framework/services/loading.service.spec.ts @@ -0,0 +1,114 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. + */ + +import { Event, NavigationError, NavigationStart } from '@angular/router'; +import { Subject } from 'rxjs'; + +import { LoadingService, LoadingServiceFactory } from './loading.service'; + +describe('LoadingService', () => { + let events = new Subject(); + + it('should instantiate from factory', () => { + const loadingService = LoadingServiceFactory({ events }); + + expect(loadingService).toBeDefined(); + }); + + it('should instantiate', () => { + const loadingService = new LoadingService({ events }); + + expect(loadingService).toBeDefined(); + + loadingService.ngOnDestroy(); + }); + + it('should set to loaded', () => { + const loadingService = new LoadingService({ events }); + + let state = false; + + loadingService.loading.subscribe(v => state = v); + loadingService.startLoading('1'); + + expect(state).toBeTruthy(); + }); + + it('should set to loaded on navigation start', () => { + const loadingService = new LoadingService({ events }); + + let state = false; + + loadingService.loading.subscribe(v => state = v); + + events.next(new NavigationStart(0, '')); + + expect(state).toBeTruthy(); + }); + + it('should not unset from loaded immediately', () => { + const loadingService = new LoadingService({ events }); + + let state = false; + + loadingService.loading.subscribe(v => state = v); + loadingService.startLoading('1'); + loadingService.completeLoading('1'); + + expect(state).toBeTruthy(); + }); + + it('should not unset from loaded delayed', (cb) => { + const loadingService = new LoadingService({ events }); + + let state = false; + + loadingService.loading.subscribe(v => state = v); + loadingService.startLoading('1'); + loadingService.completeLoading('1'); + + setTimeout(() => { + expect(state).toBeFalsy(); + + cb(); + }, 400); + }); + + it('should not unset from loaded delayed on navigation event', (cb) => { + const loadingService = new LoadingService({ events }); + + let state = false; + + loadingService.loading.subscribe(v => state = v); + events.next(new NavigationStart(0, '')); + events.next(new NavigationError(0, '', 0)); + + setTimeout(() => { + expect(state).toBeFalsy(); + + cb(); + }, 400); + }); + + it('should set back to loaded after several completions', (cb) => { + const loadingService = new LoadingService({ events }); + + let state = false; + + loadingService.loading.subscribe(v => state = v); + loadingService.startLoading('1'); + loadingService.completeLoading('1'); + loadingService.completeLoading('1'); + loadingService.startLoading('2'); + + setTimeout(() => { + expect(state).toBeTruthy(); + + cb(); + }, 400); + }); +}); diff --git a/src/Squidex/app/framework/services/loading.service.ts b/src/Squidex/app/framework/services/loading.service.ts new file mode 100644 index 000000000..78598a271 --- /dev/null +++ b/src/Squidex/app/framework/services/loading.service.ts @@ -0,0 +1,68 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. + */ + +import { Injectable, OnDestroy } from '@angular/core'; +import { NavigationCancel, NavigationEnd, NavigationError, NavigationStart, Router } from '@angular/router'; +import { BehaviorSubject, Observable, Subscription } from 'rxjs'; +import { map } from 'rxjs/operators'; + +import { Types } from './../utils/types'; + +export const LoadingServiceFactory = (router: Router) => { + return new LoadingService(router); +}; + +@Injectable() +export class LoadingService implements OnDestroy { + private readonly routerSubscription: Subscription; + private readonly loading$ = new BehaviorSubject(0); + private readonly loadingOperations: { [key: string]: boolean } = {}; + + public get loading(): Observable { + return this.loading$.pipe(map(x => x > 0)); + } + + constructor(router: Router) { + this.routerSubscription = + router.events.subscribe(event => { + if (Types.is(event, NavigationStart)) { + this.startLoading(event.id.toString()); + } else if ( + Types.is(event, NavigationEnd) || + Types.is(event, NavigationCancel) || + Types.is(event, NavigationError)) { + this.completeLoading(event.id.toString()); + } + }); + } + + public ngOnDestroy() { + this.routerSubscription.unsubscribe(); + } + + public startLoading(key: string) { + if (!this.loadingOperations[key]) { + this.loadingOperations[key] = true; + + this.loading$.next(this.loading$.value + 1); + } + } + + public completeLoading(key: string) { + if (this.loadingOperations[key]) { + delete this.loadingOperations[key]; + + setTimeout(() => { + const value = this.loading$.value; + + if (value > 0) { + this.loading$.next(value - 1); + } + }, 250); + } + } +} \ No newline at end of file diff --git a/src/Squidex/app/shared/components/asset.component.html b/src/Squidex/app/shared/components/asset.component.html index 94a882c16..b3399d2c8 100644 --- a/src/Squidex/app/shared/components/asset.component.html +++ b/src/Squidex/app/shared/components/asset.component.html @@ -7,7 +7,7 @@
- +
@@ -89,7 +89,7 @@
- +
diff --git a/src/Squidex/app/shared/components/asset.component.scss b/src/Squidex/app/shared/components/asset.component.scss index 1caa49e4b..0ccea99d0 100644 --- a/src/Squidex/app/shared/components/asset.component.scss +++ b/src/Squidex/app/shared/components/asset.component.scss @@ -287,6 +287,14 @@ $list-height: 2.375rem; cursor: pointer; } +.bg { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAIAAAC1nk4lAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAYdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjEuMWMqnEsAAACbSURBVGhD7c6hDQAxAMPA33+m7vYlJh7AoFKOBMbfyfyZRRsPgUUbD4FFGw+BRRsPgUUbD4FFGw+BRRsPgUUbD4FFGw+BRRsPgUUbD4FFGw+BRRsPgUUbD4E3o9kA7YFFGw+BRRsPgUUbD4FFGw+BRRsPgUUbD4FFGw+BRRsPgUUbD4FFGw+BRRsPgUUbD4FFGw+BRRsPgQejz7nPYYKl8IqSfgAAAABJRU5ErkJggg=='); +} + +.bg2 { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAIAAAAC64paAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAYdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjEuMWMqnEsAAAAsSURBVDhPY9iDF/zHC0Y1YwCoKhxgRGqG0jgA1AwcYFQzBoCqwgFGnuY9ewCdSg6FRg4gMAAAAABJRU5ErkJggg=='); +} + .tags { min-height: 26px; } \ No newline at end of file diff --git a/src/Squidex/app/shared/components/asset.component.ts b/src/Squidex/app/shared/components/asset.component.ts index 822d7bea2..02c40c200 100644 --- a/src/Squidex/app/shared/components/asset.component.ts +++ b/src/Squidex/app/shared/components/asset.component.ts @@ -209,7 +209,7 @@ export class AssetComponent implements OnDestroy, OnInit { private setProgress(progress: number) { this.progress = progress; - this.changeDetector.markForCheck(); + this.changeDetector.detectChanges(); } private updateAsset(asset: AssetDto, emitEvent: boolean) { @@ -224,6 +224,6 @@ export class AssetComponent implements OnDestroy, OnInit { this.renameCancel(); - this.changeDetector.markForCheck(); + this.changeDetector.detectChanges(); } } \ No newline at end of file diff --git a/src/Squidex/app/shared/components/geolocation-editor.component.ts b/src/Squidex/app/shared/components/geolocation-editor.component.ts index 88d13df64..b47014d41 100644 --- a/src/Squidex/app/shared/components/geolocation-editor.component.ts +++ b/src/Squidex/app/shared/components/geolocation-editor.component.ts @@ -272,13 +272,24 @@ export class GeolocationEditorComponent implements ControlValueAccessor, AfterVi private updateMarker(zoom: boolean, fireEvent: boolean) { if (!this.isGoogleMaps) { - this.updateMarkerOSM(zoom, fireEvent); + this.updateMarkerOSM(zoom); } else { - this.updateMarkerGoogle(zoom, fireEvent); + this.updateMarkerGoogle(zoom); + } + + if (this.value) { + this.geolocationForm.setValue(this.value, { emitEvent: true, onlySelf: false }); + } else { + this.geolocationForm.reset(undefined, { emitEvent: true, onlySelf: false }); + } + + if (fireEvent) { + this.callChange(this.value); + this.callTouched(); } } - private updateMarkerOSM(zoom: boolean, fireEvent: boolean) { + private updateMarkerOSM(zoom: boolean) { if (this.value) { if (!this.marker) { this.marker = L.marker([0, 90], { draggable: true }).addTo(this.map); @@ -307,8 +318,6 @@ export class GeolocationEditorComponent implements ControlValueAccessor, AfterVi } this.marker.setLatLng(latLng); - - this.geolocationForm.setValue(this.value, { emitEvent: false, onlySelf: false }); } else { if (this.marker) { this.marker.removeFrom(this.map); @@ -316,17 +325,10 @@ export class GeolocationEditorComponent implements ControlValueAccessor, AfterVi } this.map.fitWorld(); - - this.geolocationForm.reset(undefined, { emitEvent: false, onlySelf: false }); - } - - if (fireEvent) { - this.callChange(this.value); - this.callTouched(); } } - private updateMarkerGoogle(zoom: boolean, fireEvent: boolean) { + private updateMarkerGoogle(zoom: boolean) { if (this.value) { if (!this.marker) { this.marker = new google.maps.Marker({ @@ -361,8 +363,6 @@ export class GeolocationEditorComponent implements ControlValueAccessor, AfterVi this.marker.setPosition(latLng); this.map.setZoom(12); - - this.geolocationForm.setValue(this.value, { emitEvent: false, onlySelf: false }); } else { if (this.marker) { this.marker.setMap(null); @@ -370,13 +370,6 @@ export class GeolocationEditorComponent implements ControlValueAccessor, AfterVi } this.map.setCenter({ lat: 0, lng: 0 }); - - this.geolocationForm.reset(undefined, { emitEvent: false, onlySelf: false }); - } - - if (fireEvent) { - this.callChange(this.value); - this.callTouched(); } } } \ No newline at end of file diff --git a/src/Squidex/app/shared/components/help.component.ts b/src/Squidex/app/shared/components/help.component.ts index c7679636c..19059f168 100644 --- a/src/Squidex/app/shared/components/help.component.ts +++ b/src/Squidex/app/shared/components/help.component.ts @@ -17,8 +17,7 @@ import { HelpService } from '@app/shared/internal'; changeDetection: ChangeDetectionStrategy.OnPush }) export class HelpComponent { - public helpSections = - this.helpService.getHelp(this.route.snapshot.data['helpPage']); + public helpSections = this.helpService.getHelp(this.route.snapshot.data['helpPage']); constructor( private readonly helpService: HelpService, diff --git a/src/Squidex/app/shared/components/history-list.component.html b/src/Squidex/app/shared/components/history-list.component.html index 1581365f1..516f50197 100644 --- a/src/Squidex/app/shared/components/history-list.component.html +++ b/src/Squidex/app/shared/components/history-list.component.html @@ -1,5 +1,5 @@ -
+
diff --git a/src/Squidex/app/shared/components/pipes.ts b/src/Squidex/app/shared/components/pipes.ts index 860296f39..a5096bfe0 100644 --- a/src/Squidex/app/shared/components/pipes.ts +++ b/src/Squidex/app/shared/components/pipes.ts @@ -44,7 +44,7 @@ class UserAsyncPipe implements OnDestroy { this.subscription = transform(this.users).subscribe(value => { this.lastValue = value; - this.changeDetector.markForCheck(); + this.changeDetector.detectChanges(); }); } diff --git a/src/Squidex/app/shell/pages/internal/internal-area.component.html b/src/Squidex/app/shell/pages/internal/internal-area.component.html index c6bbe47b6..ddd4dcbb5 100644 --- a/src/Squidex/app/shell/pages/internal/internal-area.component.html +++ b/src/Squidex/app/shell/pages/internal/internal-area.component.html @@ -1,6 +1,12 @@