forked from CIT/Vmeda.Online
		
	MOBILE-3320 core: Support alerts on top of loading
This commit is contained in:
		
							parent
							
								
									70f1bd6063
								
							
						
					
					
						commit
						027c3870fd
					
				@ -13,43 +13,155 @@
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
import { CoreUtils } from '@services/utils/utils';
 | 
			
		||||
import { LoadingController } from '@singletons';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Dismiss listener.
 | 
			
		||||
 */
 | 
			
		||||
export type CoreIonLoadingElementDismissListener = () => unknown;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Class to improve the behaviour of HTMLIonLoadingElement.
 | 
			
		||||
 * It's not a subclass of HTMLIonLoadingElement because we cannot override the dismiss function.
 | 
			
		||||
 *
 | 
			
		||||
 * In addition to present/dismiss, this loader can also be paused/resumed in order to allow stacking
 | 
			
		||||
 * modals in top of one another without interfering. Conceptually, a paused loader is still
 | 
			
		||||
 * active but will not be shown in the UI.
 | 
			
		||||
 */
 | 
			
		||||
export class CoreIonLoadingElement {
 | 
			
		||||
 | 
			
		||||
    protected isPresented = false;
 | 
			
		||||
    protected isDismissed = false;
 | 
			
		||||
    protected scheduled = false;
 | 
			
		||||
    protected paused = false;
 | 
			
		||||
    protected listeners: CoreIonLoadingElementDismissListener[] = [];
 | 
			
		||||
    protected asyncLoadingElement?: Promise<HTMLIonLoadingElement>;
 | 
			
		||||
 | 
			
		||||
    constructor(public loading: HTMLIonLoadingElement) { }
 | 
			
		||||
    constructor(protected text?: string) { }
 | 
			
		||||
 | 
			
		||||
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
 | 
			
		||||
    async dismiss(data?: any, role?: string): Promise<boolean> {
 | 
			
		||||
        if (!this.isPresented || this.isDismissed) {
 | 
			
		||||
            this.isDismissed = true;
 | 
			
		||||
    /**
 | 
			
		||||
     * Dismiss the loading element.
 | 
			
		||||
     *
 | 
			
		||||
     * @param data Dismiss data.
 | 
			
		||||
     * @param role Dismiss role.
 | 
			
		||||
     */
 | 
			
		||||
    async dismiss(data?: unknown, role?: string): Promise<void> {
 | 
			
		||||
        if (this.paused) {
 | 
			
		||||
            this.paused = false;
 | 
			
		||||
            this.listeners.forEach(listener => listener());
 | 
			
		||||
 | 
			
		||||
            return true;
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.isDismissed = true;
 | 
			
		||||
        if (!this.asyncLoadingElement) {
 | 
			
		||||
            if (this.scheduled) {
 | 
			
		||||
                this.scheduled = false;
 | 
			
		||||
                this.listeners.forEach(listener => listener());
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        return this.loading.dismiss(data, role);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const asyncLoadingElement = this.asyncLoadingElement;
 | 
			
		||||
        delete this.asyncLoadingElement;
 | 
			
		||||
 | 
			
		||||
        const loadingElement = await asyncLoadingElement;
 | 
			
		||||
        await loadingElement.dismiss(data, role);
 | 
			
		||||
 | 
			
		||||
        this.listeners.forEach(listener => listener());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Present the loading.
 | 
			
		||||
     * Register dismiss listener.
 | 
			
		||||
     *
 | 
			
		||||
     * @param listener Listener.
 | 
			
		||||
     */
 | 
			
		||||
    onDismiss(listener: CoreIonLoadingElementDismissListener): void {
 | 
			
		||||
        this.listeners.push(listener);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Hide the loading element.
 | 
			
		||||
     */
 | 
			
		||||
    async pause(): Promise<void> {
 | 
			
		||||
        if (!this.asyncLoadingElement) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.paused = true;
 | 
			
		||||
 | 
			
		||||
        const asyncLoadingElement = this.asyncLoadingElement;
 | 
			
		||||
        delete this.asyncLoadingElement;
 | 
			
		||||
 | 
			
		||||
        const loadingElement = await asyncLoadingElement;
 | 
			
		||||
        loadingElement.dismiss();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Present the loading element.
 | 
			
		||||
     */
 | 
			
		||||
    async present(): Promise<void> {
 | 
			
		||||
        if (this.paused || this.scheduled || this.asyncLoadingElement) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Wait a bit before presenting the modal, to prevent it being displayed if dismiss is called fast.
 | 
			
		||||
        this.scheduled = true;
 | 
			
		||||
 | 
			
		||||
        await CoreUtils.wait(40);
 | 
			
		||||
 | 
			
		||||
        if (!this.isDismissed) {
 | 
			
		||||
            this.isPresented = true;
 | 
			
		||||
        if (!this.scheduled) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
            await this.loading.present();
 | 
			
		||||
        // Present modal.
 | 
			
		||||
        this.scheduled = false;
 | 
			
		||||
 | 
			
		||||
        await this.presentLoadingElement();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Show loading element.
 | 
			
		||||
     */
 | 
			
		||||
    async resume(): Promise<void> {
 | 
			
		||||
        if (!this.paused) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.paused = false;
 | 
			
		||||
 | 
			
		||||
        await this.presentLoadingElement();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Update text in the loading element.
 | 
			
		||||
     *
 | 
			
		||||
     * @param text Text.
 | 
			
		||||
     */
 | 
			
		||||
    async updateText(text: string): Promise<void> {
 | 
			
		||||
        this.text = text;
 | 
			
		||||
 | 
			
		||||
        if (!this.asyncLoadingElement) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const loadingElement = await this.asyncLoadingElement;
 | 
			
		||||
        const contentElement = loadingElement.querySelector('.loading-content');
 | 
			
		||||
 | 
			
		||||
        if (contentElement) {
 | 
			
		||||
            contentElement.innerHTML = text;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Create and present the loading element.
 | 
			
		||||
     */
 | 
			
		||||
    private async presentLoadingElement(): Promise<void> {
 | 
			
		||||
        let resolveLoadingElement!: ((loadingElement: HTMLIonLoadingElement) => void);
 | 
			
		||||
        this.asyncLoadingElement = new Promise(resolve => resolveLoadingElement = resolve);
 | 
			
		||||
 | 
			
		||||
        const loadingElement = await LoadingController.create({ message: this.text });
 | 
			
		||||
 | 
			
		||||
        await loadingElement.present();
 | 
			
		||||
 | 
			
		||||
        resolveLoadingElement(loadingElement);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -873,10 +873,7 @@ export class CoreFileUploaderHelperProvider {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const contentElement = modal.loading?.querySelector('.loading-content');
 | 
			
		||||
        if (contentElement) {
 | 
			
		||||
            contentElement.innerHTML = Translate.instant(stringKey, { $a: perc.toFixed(1) });
 | 
			
		||||
        }
 | 
			
		||||
        modal.updateText(Translate.instant(stringKey, { $a: perc.toFixed(1) }));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -30,15 +30,7 @@ import { CoreIonLoadingElement } from '@classes/ion-loading';
 | 
			
		||||
import { CoreCanceledError } from '@classes/errors/cancelederror';
 | 
			
		||||
import { CoreAnyError, CoreError } from '@classes/errors/error';
 | 
			
		||||
import { CoreSilentError } from '@classes/errors/silenterror';
 | 
			
		||||
import {
 | 
			
		||||
    makeSingleton,
 | 
			
		||||
    Translate,
 | 
			
		||||
    AlertController,
 | 
			
		||||
    LoadingController,
 | 
			
		||||
    ToastController,
 | 
			
		||||
    PopoverController,
 | 
			
		||||
    ModalController,
 | 
			
		||||
} from '@singletons';
 | 
			
		||||
import { makeSingleton, Translate, AlertController, ToastController, PopoverController, ModalController } from '@singletons';
 | 
			
		||||
import { CoreLogger } from '@singletons/logger';
 | 
			
		||||
import { CoreFileSizeSum } from '@services/plugin-file-delegate';
 | 
			
		||||
import { CoreNetworkError } from '@classes/errors/network-error';
 | 
			
		||||
@ -66,6 +58,7 @@ export class CoreDomUtilsProvider {
 | 
			
		||||
    protected lastInstanceId = 0;
 | 
			
		||||
    protected debugDisplay = false; // Whether to display debug messages. Store it in a variable to make it synchronous.
 | 
			
		||||
    protected displayedAlerts: Record<string, HTMLIonAlertElement> = {}; // To prevent duplicated alerts.
 | 
			
		||||
    protected activeLoadingModals: CoreIonLoadingElement[] = [];
 | 
			
		||||
    protected logger: CoreLogger;
 | 
			
		||||
 | 
			
		||||
    constructor(protected domSanitizer: DomSanitizer) {
 | 
			
		||||
@ -1216,6 +1209,10 @@ export class CoreDomUtilsProvider {
 | 
			
		||||
 | 
			
		||||
        const alert = await AlertController.create(options);
 | 
			
		||||
 | 
			
		||||
        if (Object.keys(this.displayedAlerts).length === 0) {
 | 
			
		||||
            await Promise.all(this.activeLoadingModals.slice(0).reverse().map(modal => modal.pause()));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // eslint-disable-next-line promise/catch-or-return
 | 
			
		||||
        alert.present().then(() => {
 | 
			
		||||
            if (hasHTMLTags) {
 | 
			
		||||
@ -1231,9 +1228,14 @@ export class CoreDomUtilsProvider {
 | 
			
		||||
        this.displayedAlerts[alertId] = alert;
 | 
			
		||||
 | 
			
		||||
        // Set the callbacks to trigger an observable event.
 | 
			
		||||
        // eslint-disable-next-line promise/catch-or-return, promise/always-return
 | 
			
		||||
        alert.onDidDismiss().then(() => {
 | 
			
		||||
        // eslint-disable-next-line promise/catch-or-return
 | 
			
		||||
        alert.onDidDismiss().then(async () => {
 | 
			
		||||
            delete this.displayedAlerts[alertId];
 | 
			
		||||
 | 
			
		||||
            // eslint-disable-next-line promise/always-return
 | 
			
		||||
            if (Object.keys(this.displayedAlerts).length === 0) {
 | 
			
		||||
                await Promise.all(this.activeLoadingModals.map(modal => modal.resume()));
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        if (autocloseTime && autocloseTime > 0) {
 | 
			
		||||
@ -1447,11 +1449,17 @@ export class CoreDomUtilsProvider {
 | 
			
		||||
            text = Translate.instant(text);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const loadingElement = await LoadingController.create({
 | 
			
		||||
            message: text,
 | 
			
		||||
        const loading = new CoreIonLoadingElement(text);
 | 
			
		||||
 | 
			
		||||
        loading.onDismiss(() => {
 | 
			
		||||
            const index = this.activeLoadingModals.indexOf(loading);
 | 
			
		||||
 | 
			
		||||
            if (index !== -1) {
 | 
			
		||||
                this.activeLoadingModals.splice(index, 1);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        const loading = new CoreIonLoadingElement(loadingElement);
 | 
			
		||||
        this.activeLoadingModals.push(loading);
 | 
			
		||||
 | 
			
		||||
        await loading.present();
 | 
			
		||||
 | 
			
		||||
@ -1480,19 +1488,19 @@ export class CoreDomUtilsProvider {
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const alert = await AlertController.create({
 | 
			
		||||
        const alert = await this.showAlertWithOptions({
 | 
			
		||||
            message: message,
 | 
			
		||||
            buttons: buttons,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        await alert.present();
 | 
			
		||||
 | 
			
		||||
        const isDevice = CoreApp.isAndroid() || CoreApp.isIOS();
 | 
			
		||||
        if (!isDevice) {
 | 
			
		||||
            // Treat all anchors so they don't override the app.
 | 
			
		||||
            const alertMessageEl: HTMLElement | null = alert.querySelector('.alert-message');
 | 
			
		||||
            alertMessageEl && this.treatAnchors(alertMessageEl);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await alert.onDidDismiss();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user