From 4fd4a4d76d0fbe01256464f8f08d5efc449b4e84 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Thu, 22 Oct 2020 14:58:26 +0200 Subject: [PATCH] A few UI fixes. --- .../angular/routers/router-2-state.spec.ts | 70 ++++++++++++++++--- .../angular/routers/router-2-state.ts | 58 +++++++++++++-- .../framework/services/analytics.service.ts | 2 +- .../forms/language-selector.component.html | 2 +- .../forms/language-selector.component.scss | 4 ++ frontend/app/shared/state/assets.state.ts | 2 +- frontend/app/shared/state/contents.state.ts | 2 +- frontend/app/shared/state/query.ts | 4 ++ 8 files changed, 126 insertions(+), 18 deletions(-) diff --git a/frontend/app/framework/angular/routers/router-2-state.spec.ts b/frontend/app/framework/angular/routers/router-2-state.spec.ts index 500fcf0ea..1e38600dd 100644 --- a/frontend/app/framework/angular/routers/router-2-state.spec.ts +++ b/frontend/app/framework/angular/routers/router-2-state.spec.ts @@ -5,9 +5,9 @@ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ -import { NavigationExtras, Params, Router } from '@angular/router'; +import { NavigationEnd, NavigationExtras, NavigationStart, Params, Router } from '@angular/router'; import { LocalStoreService, MathHelper, Pager } from '@app/framework/internal'; -import { BehaviorSubject } from 'rxjs'; +import { BehaviorSubject, Subject } from 'rxjs'; import { IMock, It, Mock, Times } from 'typemoq'; import { State } from './../../state'; import { PagerSynchronizer, Router2State, StringKeysSynchronizer, StringSynchronizer } from './router-2-state'; @@ -208,7 +208,8 @@ describe('Router2State', () => { describe('Implementation', () => { let localStore: IMock; let routerQueryParams: BehaviorSubject; - let routeActivated: any; + let routerEvents: Subject; + let route: any; let router: IMock; let router2State: Router2State; let state: State; @@ -217,14 +218,16 @@ describe('Router2State', () => { beforeEach(() => { localStore = Mock.ofType(); + routerEvents = new Subject(); router = Mock.ofType(); + router.setup(x => x.events).returns(() => routerEvents); state = new State({}); routerQueryParams = new BehaviorSubject({}); - routeActivated = { queryParams: routerQueryParams, id: MathHelper.guid() }; - router2State = new Router2State(routeActivated, router.object, localStore.object); + route = { queryParams: routerQueryParams, id: MathHelper.guid() }; + router2State = new Router2State(route, router.object, localStore.object); router2State.mapTo(state) .keep('keep') .withString('state1', 'key1') @@ -242,8 +245,9 @@ describe('Router2State', () => { it('should unsubscribe from route and state', () => { router2State.ngOnDestroy(); - expect(state.changes['observers'].length).toBe(0); - expect(routeActivated.queryParams.observers.length).toBe(0); + expect(state.changes['observers'].length).toEqual(0); + expect(route.queryParams.observers.length).toEqual(0); + expect(routerEvents.observers.length).toEqual(0); }); it('Should sync from route', () => { @@ -279,6 +283,22 @@ describe('Router2State', () => { expect(invoked).toEqual(1); }); + it('Should not sync again when no value has changed', () => { + routerQueryParams.next({ + key1: 'hello', + key2: 'squidex' + }); + + routerQueryParams.next({ + key1: 'hello', + key2: 'squidex', + key3: undefined, + key4: null + }); + + expect(invoked).toEqual(1); + }); + it('Should sync again when new query changed', () => { routerQueryParams.next({ key1: 'hello', @@ -327,7 +347,41 @@ describe('Router2State', () => { state2: 'squidex' }); - expect(routeExtras!.relativeTo).toBeDefined(); + expect(routeExtras!.replaceUrl).toBeTrue(); + expect(routeExtras!.queryParamsHandling).toBe('merge'); + expect(routeExtras!.queryParams).toEqual({ key1: 'hello', key2: 'squidex' }); + }); + + it('Should not sync when navigating', () => { + routerEvents.next(new NavigationStart(0, '')); + + state.next({ + state1: 'hello', + state2: 'squidex' + }); + + router.verify(x => x.navigate(It.isAny(), It.isAny()), Times.never()); + + expect().nothing(); + }); + + it('Should sync from state delayed when navigating', () => { + let routeExtras: NavigationExtras; + + router.setup(x => x.navigate([], It.isAny())) + .callback((_, extras) => { routeExtras = extras; }); + + routerEvents.next(new NavigationStart(0, '')); + + state.next({ + state1: 'hello', + state2: 'squidex' + }); + + router.verify(x => x.navigate(It.isAny(), It.isAny()), Times.never()); + + routerEvents.next(new NavigationEnd(0, '', '')); + expect(routeExtras!.replaceUrl).toBeTrue(); expect(routeExtras!.queryParamsHandling).toBe('merge'); expect(routeExtras!.queryParams).toEqual({ key1: 'hello', key2: 'squidex' }); diff --git a/frontend/app/framework/angular/routers/router-2-state.ts b/frontend/app/framework/angular/routers/router-2-state.ts index d47616305..d7f96e262 100644 --- a/frontend/app/framework/angular/routers/router-2-state.ts +++ b/frontend/app/framework/angular/routers/router-2-state.ts @@ -8,7 +8,7 @@ // tslint:disable: readonly-array import { Injectable, OnDestroy } from '@angular/core'; -import { ActivatedRoute, Params, Router } from '@angular/router'; +import { ActivatedRoute, NavigationCancel, NavigationEnd, NavigationError, NavigationStart, Params, Router } from '@angular/router'; import { LocalStoreService, Pager, Types } from '@app/framework/internal'; import { State } from '@app/framework/state'; import { Subscription } from 'rxjs'; @@ -177,6 +177,9 @@ export class Router2StateMap implements StateSynchronizerMap, @@ -194,6 +197,23 @@ export class Router2StateMap implements StateSynchronizerMap this.syncToRoute(s)); + + this.subscriptionEvents = + this.router.events + .subscribe(event => { + if (Types.is(event, NavigationStart)) { + this.isNavigating = true; + } else if ( + Types.is(event, NavigationEnd) || + Types.is(event, NavigationCancel) || + Types.is(event, NavigationError)) { + this.isNavigating = false; + + if (this.pendingParams) { + this.syncFromParams(this.pendingParams); + } + } + }); } public destroy() { @@ -201,6 +221,7 @@ export class Router2StateMap implements StateSynchronizerMap implements StateSynchronizerMap implements StateSynchronizerMap Types.is(e, NavigationEnd))) + filter(event => Types.is(event, NavigationEnd))) .subscribe(() => { this.gtag('config', this.analyticsId, { page_path: window.location.pathname, anonymize_ip: true }); }); diff --git a/frontend/app/shared/components/forms/language-selector.component.html b/frontend/app/shared/components/forms/language-selector.component.html index e24ec3cf3..21e7221d2 100644 --- a/frontend/app/shared/components/forms/language-selector.component.html +++ b/frontend/app/shared/components/forms/language-selector.component.html @@ -9,7 +9,7 @@ {{selectedLanguage.iso2Code}} - +