diff --git a/client/src/app/app.component.ts b/client/src/app/app.component.ts index bda3c4386..a8387db52 100644 --- a/client/src/app/app.component.ts +++ b/client/src/app/app.component.ts @@ -2,7 +2,7 @@ import { ApplicationRef, Component } from '@angular/core'; import { Router } from '@angular/router'; import { TranslateService } from '@ngx-translate/core'; -import { auditTime, filter, take } from 'rxjs/operators'; +import { filter, take } from 'rxjs/operators'; import { ConfigService } from './core/ui-services/config.service'; import { ConstantsService } from './core/core-services/constants.service'; @@ -17,7 +17,6 @@ import { PrioritizeService } from './core/core-services/prioritize.service'; import { RoutingStateService } from './core/ui-services/routing-state.service'; import { ServertimeService } from './core/core-services/servertime.service'; import { ThemeService } from './core/ui-services/theme.service'; -import { ViewUser } from './site/users/models/view-user'; declare global { /** @@ -46,16 +45,6 @@ declare global { styleUrls: ['./app.component.scss'] }) export class AppComponent { - /** - * Member to hold the state of `stable`. - */ - private isStable: boolean; - - /** - * Member to hold the user. - */ - private user: ViewUser; - /** * Master-component of all apps. * @@ -79,11 +68,11 @@ export class AppComponent { appRef: ApplicationRef, servertimeService: ServertimeService, router: Router, - private operator: OperatorService, + operator: OperatorService, loginDataService: LoginDataService, constantsService: ConstantsService, // Needs to be started, so it can register itself to the WebsocketService themeService: ThemeService, - private overlayService: OverlayService, + overlayService: OverlayService, countUsersService: CountUsersService, // Needed to register itself. configService: ConfigService, loadFontService: LoadFontService, @@ -115,31 +104,6 @@ export class AppComponent { take(1) ) .subscribe(() => servertimeService.startScheduler()); - - // Subscribe to hide the spinner if the application has changed. - appRef.isStable - .pipe( - filter(s => s), - auditTime(1000) - ) - .subscribe(stable => { - // check the stable state only once. - if (!this.isStable && stable) { - this.isStable = true; - this.checkConnectionProgress(); - } - }); - // subscribe, to get the user. - operator.getViewUserObservable().subscribe(user => { - // check the user only once - if ((!this.user && user) || operator.isAnonymous) { - this.user = user; - this.checkConnectionProgress(); - // if the user is logging out, remove this user. - } else if (user === null) { - this.user = user; - } - }); } /** @@ -192,14 +156,4 @@ export class AppComponent { return ((this % n) + n) % n; }; } - - /** - * Function to check if the user is existing and the app is already stable. - * If both conditions true, hide the spinner. - */ - private checkConnectionProgress(): void { - if ((this.user || this.operator.isAnonymous) && this.isStable) { - this.overlayService.setSpinner(false, null, false, true); - } - } } diff --git a/client/src/app/core/core-services/auth.service.ts b/client/src/app/core/core-services/auth.service.ts index 887467742..9363ed217 100644 --- a/client/src/app/core/core-services/auth.service.ts +++ b/client/src/app/core/core-services/auth.service.ts @@ -48,7 +48,9 @@ export class AuthService { }; const response = await this.http.post(environment.urlPrefix + '/users/login/', user); earlySuccessCallback(); + await this.OpenSlides.shutdown(); await this.operator.setWhoAmI(response); + await this.OpenSlides.afterLoginBootup(response.user_id); await this.redirectUser(response.user_id); return response; } diff --git a/client/src/app/core/core-services/data-store-upgrade.service.ts b/client/src/app/core/core-services/data-store-upgrade.service.ts index bc7e9e51e..f06346d93 100644 --- a/client/src/app/core/core-services/data-store-upgrade.service.ts +++ b/client/src/app/core/core-services/data-store-upgrade.service.ts @@ -1,5 +1,7 @@ import { Injectable } from '@angular/core'; +import { BehaviorSubject } from 'rxjs'; + import { AutoupdateService } from './autoupdate.service'; import { ConstantsService } from './constants.service'; import { StorageService } from './storage.service'; @@ -26,6 +28,11 @@ const SCHEMA_VERSION = 'SchemaVersion'; providedIn: 'root' }) export class DataStoreUpgradeService { + /** + * Notify, when upgrade has checked. + */ + public readonly upgradeChecked = new BehaviorSubject(false); + /** * @param autoupdateService * @param constantsService @@ -48,6 +55,7 @@ export class DataStoreUpgradeService { } public async checkForUpgrade(serverVersion: SchemaVersion): Promise { + this.upgradeChecked.next(false); console.log('Server schema version:', serverVersion); const clientVersion = await this.storageService.get(SCHEMA_VERSION); await this.storageService.set(SCHEMA_VERSION, serverVersion); @@ -77,6 +85,7 @@ export class DataStoreUpgradeService { } else { console.log('\t-> No upgrade needed.'); } + this.upgradeChecked.next(true); return doUpgrade; } } diff --git a/client/src/app/core/core-services/openslides.service.ts b/client/src/app/core/core-services/openslides.service.ts index 8dbfdf351..2d6f31411 100644 --- a/client/src/app/core/core-services/openslides.service.ts +++ b/client/src/app/core/core-services/openslides.service.ts @@ -1,6 +1,8 @@ import { Injectable } from '@angular/core'; import { Router } from '@angular/router'; +import { BehaviorSubject } from 'rxjs'; + import { AutoupdateService } from './autoupdate.service'; import { ConstantsService } from './constants.service'; import { DataStoreService } from './data-store.service'; @@ -20,14 +22,17 @@ export class OpenSlidesService { */ public redirectUrl: string; + /** + * Subject to hold the flag `booted`. + */ + public readonly booted = new BehaviorSubject(false); + /** * Saves, if OpenSlides is fully booted. This means, that a user must be logged in * (Anonymous is also a user in this case). This is the case after `afterLoginBootup`. */ - private _booted = false; - - public get booted(): boolean { - return this._booted; + public get isBooted(): boolean { + return this.booted.value; } /** @@ -113,7 +118,7 @@ export class OpenSlidesService { } await this.setupDataStoreAndWebSocket(); // Now finally booted. - this._booted = true; + this.booted.next(true); } /** @@ -139,7 +144,7 @@ export class OpenSlidesService { */ public async shutdown(): Promise { await this.websocketService.close(); - this._booted = false; + this.booted.next(false); } /** diff --git a/client/src/app/core/ui-services/overlay.service.ts b/client/src/app/core/ui-services/overlay.service.ts index de560e477..99f50e699 100644 --- a/client/src/app/core/ui-services/overlay.service.ts +++ b/client/src/app/core/ui-services/overlay.service.ts @@ -5,6 +5,9 @@ import { Observable, Subject } from 'rxjs'; import { largeDialogSettings } from 'app/shared/utils/dialog-settings'; import { SuperSearchComponent } from 'app/site/common/components/super-search/super-search.component'; +import { DataStoreUpgradeService } from '../core-services/data-store-upgrade.service'; +import { OpenSlidesService } from '../core-services/openslides.service'; +import { OperatorService } from '../core-services/operator.service'; /** * Component to control the visibility of components, that overlay the whole window. @@ -26,28 +29,66 @@ export class OverlayService { private spinner: Subject<{ isVisible: boolean; text?: string }> = new Subject(); /** - * Boolean, whether appearing of the spinner should be prevented next time. + * Flag, that indicates, if the upgrade has checked. */ - private preventAppearingSpinner = false; + private upgradeChecked = false; + + /** + * The current user. + */ + private user = null; + + /** + * Flag, whether the app has booted. + */ + private hasBooted = false; /** * * @param dialogService Injects the `MatDialog` to show the `super-search.component` */ - public constructor(private dialogService: MatDialog) {} + public constructor( + private dialogService: MatDialog, + private operator: OperatorService, + OpenSlides: OpenSlidesService, + upgradeService: DataStoreUpgradeService + ) { + // Subscribe to the current user. + operator.getViewUserObservable().subscribe(user => { + if (user) { + this.user = user; + this.checkConnection(); + } + }); + // Subscribe to the booting-step. + OpenSlides.booted.subscribe(isBooted => { + this.hasBooted = isBooted; + this.checkConnection(); + }); + // Subscribe to the upgrade-mechanism. + upgradeService.upgradeChecked.subscribe(upgradeDone => { + this.upgradeChecked = upgradeDone; + this.checkConnection(); + }); + } /** - * Function to change the visibility of the `global-spinner.component`. + * Function to show the `global-spinner.component`. * - * @param isVisible flag, if the spinner should be shown. * @param text optional. If the spinner should show a message. - * @param preventAppearing optional. Wether to prevent showing the spinner the next time. + * @param forceAppearing optional. If the spinner must be shown. */ - public setSpinner(isVisible: boolean, text?: string, forceAppearing?: boolean, preventAppearing?: boolean): void { - if (!this.preventAppearingSpinner || forceAppearing) { - setTimeout(() => this.spinner.next({ isVisible, text })); + public showSpinner(text?: string, forceAppearing?: boolean): void { + if (!this.isConnectionStable() || forceAppearing) { + setTimeout(() => this.spinner.next({ isVisible: true, text })); } - this.preventAppearingSpinner = preventAppearing; + } + + /** + * Function to hide the `global-spinner.component`. + */ + public hideSpinner(): void { + setTimeout(() => this.spinner.next({ isVisible: false })); } /** @@ -78,6 +119,25 @@ export class OverlayService { } } + /** + * Checks, if the connection is stable. + * This relates to `appStable`, `booted` and `user || anonymous`. + * + * @returns True, if the three booleans are all true. + */ + public isConnectionStable(): boolean { + return this.upgradeChecked && this.hasBooted && (!!this.user || this.operator.isAnonymous); + } + + /** + * Function to check, if the app is stable and, if true, hide the spinner. + */ + private checkConnection(): void { + if (this.isConnectionStable()) { + this.hideSpinner(); + } + } + /** * Function to reset the properties for the spinner. * @@ -85,6 +145,8 @@ export class OverlayService { * and still stays at the website. */ public logout(): void { - this.preventAppearingSpinner = false; + this.hasBooted = false; + this.user = null; + this.upgradeChecked = false; } } diff --git a/client/src/app/site/login/components/login-mask/login-mask.component.ts b/client/src/app/site/login/components/login-mask/login-mask.component.ts index 0e37c6ecc..7adfe1275 100644 --- a/client/src/app/site/login/components/login-mask/login-mask.component.ts +++ b/client/src/app/site/login/components/login-mask/login-mask.component.ts @@ -52,6 +52,11 @@ export class LoginMaskComponent extends BaseViewComponent implements OnInit, OnD public operatorSubscription: Subscription | null; + /** + * The message, that should appear, when the user logs in. + */ + private loginMessage = 'Loading data. Please wait...'; + /** * Constructor for the login component * @@ -136,11 +141,12 @@ export class LoginMaskComponent extends BaseViewComponent implements OnInit, OnD public async formLogin(): Promise { this.loginErrorMsg = ''; try { + this.overlayService.showSpinner(this.translate.instant(this.loginMessage), true); await this.authService.login(this.loginForm.value.username, this.loginForm.value.password, () => { - this.overlayService.setSpinner(true, this.translate.instant('Loading data. Please wait...')); this.clearOperatorSubscription(); // We take control, not the subscription. }); } catch (e) { + this.overlayService.hideSpinner(); this.loginForm.setErrors({ notFound: true }); @@ -166,6 +172,7 @@ export class LoginMaskComponent extends BaseViewComponent implements OnInit, OnD * Guests (if enabled) can navigate directly to the main page. */ public guestLogin(): void { + this.overlayService.showSpinner(this.translate.instant(this.loginMessage)); this.authService.guestLogin(); } } diff --git a/client/src/app/site/motions/modules/motion-list/components/motion-list/motion-list.component.ts b/client/src/app/site/motions/modules/motion-list/components/motion-list/motion-list.component.ts index 9d0a887fe..d59d0ffdb 100644 --- a/client/src/app/site/motions/modules/motion-list/components/motion-list/motion-list.component.ts +++ b/client/src/app/site/motions/modules/motion-list/components/motion-list/motion-list.component.ts @@ -380,7 +380,7 @@ export class MotionListComponent extends BaseListViewComponent imple } catch (e) { this.raiseError(e); } finally { - this.overlayService.setSpinner(false); + this.overlayService.hideSpinner(); } } diff --git a/client/src/app/site/motions/services/motion-multiselect.service.ts b/client/src/app/site/motions/services/motion-multiselect.service.ts index 4f31257cb..af054cfa8 100644 --- a/client/src/app/site/motions/services/motion-multiselect.service.ts +++ b/client/src/app/site/motions/services/motion-multiselect.service.ts @@ -81,10 +81,10 @@ export class MotionMultiselectService { `\n${i} ` + this.translate.instant('of') + ` ${motions.length}`; - this.overlayService.setSpinner(true, message, true); + this.overlayService.showSpinner(message, true); await this.repo.delete(motion); } - this.overlayService.setSpinner(false); + this.overlayService.hideSpinner(); } } @@ -119,7 +119,7 @@ export class MotionMultiselectService { const selectedChoice = await this.choiceService.open(title, choices); if (selectedChoice) { const message = `${motions.length} ` + this.translate.instant(this.messageForSpinner); - this.overlayService.setSpinner(true, message, true); + this.overlayService.showSpinner(message, true); await this.repo.setMultiState(motions, selectedChoice.items as number); } } else { @@ -152,7 +152,7 @@ export class MotionMultiselectService { recommendation: selectedChoice.action ? 0 : (selectedChoice.items as number) })); const message = `${motions.length} ` + this.translate.instant(this.messageForSpinner); - this.overlayService.setSpinner(true, message, true); + this.overlayService.showSpinner(message, true); await this.httpService.post('/rest/motions/motion/manage_multiple_recommendation/', { motions: requestData }); @@ -181,7 +181,7 @@ export class MotionMultiselectService { ); if (selectedChoice) { const message = this.translate.instant(this.messageForSpinner); - this.overlayService.setSpinner(true, message, true); + this.overlayService.showSpinner(message, true); await this.repo.setMultiCategory(motions, selectedChoice.items as number); } } @@ -220,7 +220,7 @@ export class MotionMultiselectService { } const message = `${motions.length} ` + this.translate.instant(this.messageForSpinner); - this.overlayService.setSpinner(true, message, true); + this.overlayService.showSpinner(message, true); await this.httpService.post('/rest/motions/motion/manage_multiple_submitters/', { motions: requestData }); } } @@ -268,7 +268,7 @@ export class MotionMultiselectService { } const message = `${motions.length} ` + this.translate.instant(this.messageForSpinner); - this.overlayService.setSpinner(true, message, true); + this.overlayService.showSpinner(message, true); await this.httpService.post('/rest/motions/motion/manage_multiple_tags/', { motions: requestData }); } } @@ -290,7 +290,7 @@ export class MotionMultiselectService { ); if (selectedChoice) { const message = this.translate.instant(this.messageForSpinner); - this.overlayService.setSpinner(true, message, true); + this.overlayService.showSpinner(message, true); const blockId = selectedChoice.action ? null : (selectedChoice.items as number); await this.repo.setMultiMotionBlock(motions, blockId); } @@ -355,7 +355,7 @@ export class MotionMultiselectService { if (selectedChoice && motions.length) { const message = this.translate.instant(`I have ${motions.length} favorite motions. Please wait ...`); const star = (selectedChoice.items as number) === choices[0].id; - this.overlayService.setSpinner(true, message, true); + this.overlayService.showSpinner(message, true); await this.personalNoteService.bulkSetStar(motions, star); } } diff --git a/client/src/app/site/site.component.ts b/client/src/app/site/site.component.ts index 99fabfbf7..3766c8b1a 100644 --- a/client/src/app/site/site.component.ts +++ b/client/src/app/site/site.component.ts @@ -103,7 +103,10 @@ export class SiteComponent extends BaseComponent implements OnInit { private overlayService: OverlayService ) { super(title, translate); - overlayService.setSpinner(true, translate.instant('Loading data. Please wait...')); + overlayService.showSpinner( + translate.instant('Loading data. Please wait...'), + !(operator.guestsEnabled && operator.isAnonymous) + ); this.operator.getViewUserObservable().subscribe(user => { if (!operator.isAnonymous) { @@ -253,6 +256,9 @@ export class SiteComponent extends BaseComponent implements OnInit { * Function to log out the current user */ public logout(): void { + if (this.operator.guestsEnabled) { + this.overlayService.showSpinner(null, true); + } this.authService.logout(); this.overlayService.logout(); }