feat(ui): animated boot splash and loading screen
Show a Lottie-animated splash while the app bundle boots and dismiss it once WebAppComponent initialises, plus a reusable mifosx-loading-screen component with loading and error animations. lottie.min.js is served from the lottie-web package instead of being vendored into src/assets. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
<!--
|
||||
Copyright since 2025 Mifos Initiative
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
|
||||
@if (active) {
|
||||
@if (currentMode === 'overlay') {
|
||||
<!-- Mifos Mobile Transaction / Action Progress Overlay -->
|
||||
<div class="cbs-loading-backdrop" (click)="preventDismiss($event)">
|
||||
<div class="cbs-loading-dialog" role="dialog" aria-modal="true" [attr.aria-label]="title || 'Loading'">
|
||||
<div class="cbs-lottie-container" #lottieContainer>
|
||||
<!-- Vector Fallback if Lottie is loading -->
|
||||
@if (!lottieLoaded) {
|
||||
<div class="cbs-vector-spinner">
|
||||
<div class="spinner-ring ring-outer"></div>
|
||||
<div class="spinner-ring ring-mid"></div>
|
||||
<div class="spinner-ring ring-inner"></div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="cbs-loading-dialog__content">
|
||||
<h3 class="cbs-loading-dialog__title">{{ title || 'CBS Nexora' }}</h3>
|
||||
<p class="cbs-loading-dialog__message">{{ message || 'Please wait...' }}</p>
|
||||
</div>
|
||||
|
||||
<div class="cbs-loading-pulse-bar">
|
||||
<div class="cbs-pulse-bar__indicator"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (currentMode === 'fullscreen') {
|
||||
<!-- Full-Screen App Boot / Splash Screen -->
|
||||
<div class="cbs-fullscreen-loader">
|
||||
<div class="cbs-fullscreen-loader__brand">
|
||||
<div class="cbs-brand-icon">
|
||||
<svg width="40" height="40" viewBox="0 0 32 32" aria-hidden="true">
|
||||
<rect x="1" y="1" width="14" height="14" rx="2" fill="#2563eb" />
|
||||
<rect x="17" y="1" width="14" height="14" rx="2" fill="#0ea5e9" />
|
||||
<rect x="1" y="17" width="14" height="14" rx="2" fill="#10b981" />
|
||||
<rect x="17" y="17" width="14" height="14" rx="2" fill="#94a3b8" />
|
||||
</svg>
|
||||
</div>
|
||||
<h2 class="cbs-brand-name">{{ title || 'CBS Nexora' }}</h2>
|
||||
<span class="cbs-brand-sub">Core Banking System</span>
|
||||
</div>
|
||||
|
||||
<div class="cbs-lottie-container cbs-lottie-container--large" #lottieContainer>
|
||||
@if (!lottieLoaded) {
|
||||
<div class="cbs-vector-spinner cbs-vector-spinner--large">
|
||||
<div class="spinner-ring ring-outer"></div>
|
||||
<div class="spinner-ring ring-mid"></div>
|
||||
<div class="spinner-ring ring-inner"></div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="cbs-progress-track">
|
||||
<div class="cbs-progress-fill"></div>
|
||||
</div>
|
||||
|
||||
<p class="cbs-fullscreen-loader__status">{{ message || 'Connecting to Core Banking Engine...' }}</p>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (currentMode === 'inline') {
|
||||
<!-- Inline Component Loader -->
|
||||
<div class="cbs-inline-loader">
|
||||
<div class="cbs-lottie-container cbs-lottie-container--compact" #lottieContainer>
|
||||
@if (!lottieLoaded) {
|
||||
<div class="cbs-vector-spinner cbs-vector-spinner--compact">
|
||||
<div class="spinner-ring ring-outer"></div>
|
||||
<div class="spinner-ring ring-mid"></div>
|
||||
<div class="spinner-ring ring-inner"></div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@if (message) {
|
||||
<p class="cbs-inline-loader__message">{{ message }}</p>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,341 @@
|
||||
/**
|
||||
* Copyright since 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Overlay Mode (MifosProgressIndicatorOverlay / MifosLoadingDialog)
|
||||
========================================================================== */
|
||||
.cbs-loading-backdrop {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 99999;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgb(9 13 22 / 72%);
|
||||
backdrop-filter: blur(8px);
|
||||
animation: backdrop-fade-in 0.25s ease-out forwards;
|
||||
}
|
||||
|
||||
.cbs-loading-dialog {
|
||||
position: relative;
|
||||
width: 90%;
|
||||
max-width: 340px;
|
||||
padding: 28px 24px;
|
||||
background: #111827;
|
||||
color: #f8fafc;
|
||||
border-radius: 20px;
|
||||
border: 1px solid rgb(255 255 255 / 12%);
|
||||
box-shadow:
|
||||
0 25px 60px -15px rgb(0 0 0 / 65%),
|
||||
0 0 35px rgb(37 99 235 / 18%);
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
animation: dialog-scale-in 0.3s cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
||||
}
|
||||
|
||||
:host-context([data-theme='light']) .cbs-loading-dialog,
|
||||
:host-context(.light-theme) .cbs-loading-dialog {
|
||||
background: #fff;
|
||||
color: #0f172a;
|
||||
border-color: rgb(0 0 0 / 8%);
|
||||
box-shadow:
|
||||
0 25px 50px -12px rgb(0 0 0 / 25%),
|
||||
0 0 25px rgb(37 99 235 / 12%);
|
||||
}
|
||||
|
||||
.cbs-loading-dialog__content {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.cbs-loading-dialog__title {
|
||||
font-size: 1.05rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.01em;
|
||||
margin: 0 0 6px;
|
||||
}
|
||||
|
||||
.cbs-loading-dialog__message {
|
||||
font-size: 0.82rem;
|
||||
color: #94a3b8;
|
||||
line-height: 1.45;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
:host-context([data-theme='light']) .cbs-loading-dialog__message,
|
||||
:host-context(.light-theme) .cbs-loading-dialog__message {
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
/* Pulse bar at dialog bottom */
|
||||
.cbs-loading-pulse-bar {
|
||||
width: 100%;
|
||||
height: 3px;
|
||||
background: rgb(255 255 255 / 8%);
|
||||
border-radius: 99px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.cbs-pulse-bar__indicator {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 40%;
|
||||
background: linear-gradient(90deg, #2563eb, #38bdf8);
|
||||
border-radius: 99px;
|
||||
animation: pulse-bar-slide 1.4s infinite ease-in-out;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Full-Screen Mode (App Boot / Splash)
|
||||
========================================================================== */
|
||||
.cbs-fullscreen-loader {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 99999;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: radial-gradient(circle at center, #0f172a 0%, #090d16 100%);
|
||||
color: #f8fafc;
|
||||
padding: 24px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
:host-context([data-theme='light']) .cbs-fullscreen-loader,
|
||||
:host-context(.light-theme) .cbs-fullscreen-loader {
|
||||
background: radial-gradient(circle at center, #fff 0%, #f1f5f9 100%);
|
||||
color: #0f172a;
|
||||
}
|
||||
|
||||
.cbs-fullscreen-loader__brand {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.cbs-brand-icon {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border-radius: 16px;
|
||||
background: linear-gradient(135deg, #1e3a8a 0%, #0284c7 100%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 10px 24px rgb(14 165 233 / 35%);
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.cbs-brand-name {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 800;
|
||||
letter-spacing: -0.02em;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.cbs-brand-sub {
|
||||
font-size: 0.8rem;
|
||||
color: #94a3b8;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.cbs-progress-track {
|
||||
width: 240px;
|
||||
height: 5px;
|
||||
background: rgb(255 255 255 / 10%);
|
||||
border-radius: 99px;
|
||||
overflow: hidden;
|
||||
margin: 18px 0 12px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.cbs-progress-fill {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 45%;
|
||||
background: linear-gradient(90deg, #2563eb, #38bdf8);
|
||||
border-radius: 99px;
|
||||
box-shadow: 0 0 12px rgb(56 189 248 / 50%);
|
||||
animation: pulse-bar-slide 1.5s infinite ease-in-out;
|
||||
}
|
||||
|
||||
.cbs-fullscreen-loader__status {
|
||||
font-size: 0.82rem;
|
||||
color: #94a3b8;
|
||||
font-family: inherit;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Inline Mode
|
||||
========================================================================== */
|
||||
.cbs-inline-loader {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 32px 16px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.cbs-inline-loader__message {
|
||||
font-size: 0.82rem;
|
||||
color: #94a3b8;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Lottie & Vector Spinner Dimensions
|
||||
========================================================================== */
|
||||
.cbs-lottie-container {
|
||||
width: 110px;
|
||||
height: 110px;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.cbs-lottie-container--large {
|
||||
width: 140px;
|
||||
height: 140px;
|
||||
}
|
||||
|
||||
.cbs-lottie-container--compact {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
}
|
||||
|
||||
/* Vector Spinner Fallback (CSS Multi-Ring Loader) */
|
||||
.cbs-vector-spinner {
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.cbs-vector-spinner--large {
|
||||
width: 96px;
|
||||
height: 96px;
|
||||
}
|
||||
|
||||
.cbs-vector-spinner--compact {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
}
|
||||
|
||||
.spinner-ring {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: 50%;
|
||||
border: 3px solid transparent;
|
||||
}
|
||||
|
||||
.ring-outer {
|
||||
border-top-color: #2563eb;
|
||||
border-right-color: #2563eb;
|
||||
animation: spin-outer 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
|
||||
}
|
||||
|
||||
.ring-mid {
|
||||
inset: 6px;
|
||||
border-bottom-color: #0ea5e9;
|
||||
border-left-color: #0ea5e9;
|
||||
animation: spin-mid 1.6s cubic-bezier(0.5, 0, 0.5, 1) infinite reverse;
|
||||
}
|
||||
|
||||
.ring-inner {
|
||||
inset: 13px;
|
||||
border-top-color: #10b981;
|
||||
animation: spin-inner 0.9s cubic-bezier(0.5, 0, 0.5, 1) infinite;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Keyframe Animations
|
||||
========================================================================== */
|
||||
@keyframes backdrop-fade-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes dialog-scale-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: scale(0.92);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulse-bar-slide {
|
||||
0% {
|
||||
left: -40%;
|
||||
width: 30%;
|
||||
}
|
||||
|
||||
50% {
|
||||
left: 35%;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
100% {
|
||||
left: 100%;
|
||||
width: 30%;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spin-outer {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spin-mid {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spin-inner {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
/**
|
||||
* Copyright since 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import {
|
||||
Component,
|
||||
OnInit,
|
||||
OnDestroy,
|
||||
Input,
|
||||
ElementRef,
|
||||
ViewChild,
|
||||
AfterViewInit,
|
||||
inject,
|
||||
ChangeDetectorRef
|
||||
} from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { LoadingScreenService, LoadingConfig } from './loading-screen.service';
|
||||
|
||||
declare const lottie: any;
|
||||
|
||||
@Component({
|
||||
selector: 'mifosx-loading-screen',
|
||||
standalone: true,
|
||||
imports: [CommonModule],
|
||||
templateUrl: './loading-screen.component.html',
|
||||
styleUrls: ['./loading-screen.component.scss']
|
||||
})
|
||||
export class LoadingScreenComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
private readonly loadingService = inject(LoadingScreenService);
|
||||
private readonly cdr = inject(ChangeDetectorRef);
|
||||
|
||||
@Input() mode: 'overlay' | 'inline' | 'fullscreen' = 'overlay';
|
||||
@Input() title: string = 'CBS Nexora';
|
||||
@Input() message: string = 'Processing transaction...';
|
||||
@Input() active: boolean = false;
|
||||
@Input() isGlobalOverlay: boolean = true;
|
||||
|
||||
@ViewChild('lottieContainer') lottieContainerRef?: ElementRef<HTMLDivElement>;
|
||||
|
||||
currentMode: 'overlay' | 'inline' | 'fullscreen' = 'overlay';
|
||||
lottieLoaded: boolean = false;
|
||||
private animInstance: any = null;
|
||||
private subscription?: Subscription;
|
||||
|
||||
ngOnInit(): void {
|
||||
this.currentMode = this.mode;
|
||||
|
||||
if (this.isGlobalOverlay) {
|
||||
this.subscription = new Subscription();
|
||||
this.subscription.add(
|
||||
this.loadingService.isVisible$.subscribe((visible) => {
|
||||
this.active = visible;
|
||||
this.cdr.markForCheck();
|
||||
if (visible) {
|
||||
setTimeout(() => this.initLottie(), 50);
|
||||
} else {
|
||||
this.destroyLottie();
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
this.subscription.add(
|
||||
this.loadingService.config$.subscribe((cfg: LoadingConfig) => {
|
||||
if (cfg.title !== undefined) this.title = cfg.title;
|
||||
if (cfg.message !== undefined) this.message = cfg.message;
|
||||
if (cfg.mode !== undefined) this.currentMode = cfg.mode;
|
||||
this.cdr.markForCheck();
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
if (this.active) {
|
||||
this.initLottie();
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.subscription?.unsubscribe();
|
||||
this.destroyLottie();
|
||||
}
|
||||
|
||||
preventDismiss(event: MouseEvent): void {
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
private initLottie(): void {
|
||||
if (!this.lottieContainerRef?.nativeElement) return;
|
||||
this.destroyLottie();
|
||||
|
||||
const container = this.lottieContainerRef.nativeElement;
|
||||
|
||||
// Check if lottie-web global is available
|
||||
if (typeof lottie !== 'undefined') {
|
||||
try {
|
||||
this.animInstance = lottie.loadAnimation({
|
||||
container,
|
||||
renderer: 'svg',
|
||||
loop: true,
|
||||
autoplay: true,
|
||||
path: 'assets/animations/loading_animation.json'
|
||||
});
|
||||
this.lottieLoaded = true;
|
||||
this.cdr.markForCheck();
|
||||
return;
|
||||
} catch (err) {
|
||||
console.warn('Could not load Lottie animation, using vector fallback', err);
|
||||
}
|
||||
}
|
||||
|
||||
// Try dynamic script load of local asset if not loaded yet
|
||||
this.loadLottieScript()
|
||||
.then(() => {
|
||||
if (typeof lottie !== 'undefined' && this.lottieContainerRef?.nativeElement) {
|
||||
this.animInstance = lottie.loadAnimation({
|
||||
container: this.lottieContainerRef.nativeElement,
|
||||
renderer: 'svg',
|
||||
loop: true,
|
||||
autoplay: true,
|
||||
path: 'assets/animations/loading_animation.json'
|
||||
});
|
||||
this.lottieLoaded = true;
|
||||
this.cdr.markForCheck();
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.lottieLoaded = false;
|
||||
this.cdr.markForCheck();
|
||||
});
|
||||
}
|
||||
|
||||
private loadLottieScript(): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (typeof lottie !== 'undefined') {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
const existingScript = document.getElementById('cbs-lottie-script');
|
||||
if (existingScript) {
|
||||
existingScript.addEventListener('load', () => resolve());
|
||||
return;
|
||||
}
|
||||
|
||||
const script = document.createElement('script');
|
||||
script.id = 'cbs-lottie-script';
|
||||
script.src = 'assets/js/lottie.min.js';
|
||||
script.onload = () => resolve();
|
||||
script.onerror = () => reject();
|
||||
document.head.appendChild(script);
|
||||
});
|
||||
}
|
||||
|
||||
private destroyLottie(): void {
|
||||
if (this.animInstance) {
|
||||
try {
|
||||
this.animInstance.destroy();
|
||||
} catch (_) {}
|
||||
this.animInstance = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* Copyright since 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
|
||||
export interface LoadingConfig {
|
||||
title?: string;
|
||||
message?: string;
|
||||
mode?: 'overlay' | 'inline' | 'fullscreen';
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class LoadingScreenService {
|
||||
private readonly visibleSubject = new BehaviorSubject<boolean>(false);
|
||||
private readonly configSubject = new BehaviorSubject<LoadingConfig>({
|
||||
title: 'CBS Nexora',
|
||||
message: 'Processing transaction...',
|
||||
mode: 'overlay'
|
||||
});
|
||||
|
||||
readonly isVisible$: Observable<boolean> = this.visibleSubject.asObservable();
|
||||
readonly config$: Observable<LoadingConfig> = this.configSubject.asObservable();
|
||||
|
||||
/**
|
||||
* Show the loading screen/overlay
|
||||
* @param message Optional message to display (e.g. "Processing Transfer...")
|
||||
* @param title Optional title to display (defaults to "CBS Nexora")
|
||||
* @param mode Optional display mode ('overlay' | 'fullscreen')
|
||||
*/
|
||||
show(message?: string, title?: string, mode: 'overlay' | 'fullscreen' = 'overlay'): void {
|
||||
this.configSubject.next({
|
||||
title: title || 'CBS Nexora',
|
||||
message: message || 'Please wait...',
|
||||
mode
|
||||
});
|
||||
this.visibleSubject.next(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the loading screen/overlay
|
||||
*/
|
||||
hide(): void {
|
||||
this.visibleSubject.next(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update message while loading screen is active
|
||||
*/
|
||||
setMessage(message: string): void {
|
||||
const current = this.configSubject.getValue();
|
||||
this.configSubject.next({ ...current, message });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user