MOBILE-3833 core: Implement cancellable promise
This commit is contained in:
		
							parent
							
								
									1b6e578c41
								
							
						
					
					
						commit
						894e4a7b62
					
				
							
								
								
									
										56
									
								
								src/core/classes/cancellable-promise.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								src/core/classes/cancellable-promise.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,56 @@
 | 
			
		||||
// (C) Copyright 2015 Moodle Pty Ltd.
 | 
			
		||||
//
 | 
			
		||||
// Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
// you may not use this file except in compliance with the License.
 | 
			
		||||
// You may obtain a copy of the License at
 | 
			
		||||
//
 | 
			
		||||
//     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
//
 | 
			
		||||
// Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
// distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
// See the License for the specific language governing permissions and
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
import { CorePromise } from '@classes/promise';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Promise whose execution can be cancelled.
 | 
			
		||||
 */
 | 
			
		||||
export class CoreCancellablePromise<T = unknown> extends CorePromise<T> {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Create a new resolved promise.
 | 
			
		||||
     *
 | 
			
		||||
     * @returns Resolved promise.
 | 
			
		||||
     */
 | 
			
		||||
    static resolve(): CoreCancellablePromise<void>;
 | 
			
		||||
    static resolve<T>(result: T): CoreCancellablePromise<T>;
 | 
			
		||||
    static resolve<T>(result?: T): CoreCancellablePromise<T> {
 | 
			
		||||
        return new this(resolve => result ? resolve(result) : (resolve as () => void)(), () => {
 | 
			
		||||
            // Nothing to do here.
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected cancelPromise: () => void;
 | 
			
		||||
 | 
			
		||||
    constructor(
 | 
			
		||||
        executor: (resolve: (value: T | PromiseLike<T>) => void, reject: (reason?: Error) => void) => void,
 | 
			
		||||
        cancelPromise: () => void,
 | 
			
		||||
    ) {
 | 
			
		||||
        super(new Promise(executor));
 | 
			
		||||
 | 
			
		||||
        this.cancelPromise = cancelPromise;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Cancel promise.
 | 
			
		||||
     *
 | 
			
		||||
     * After this method is called, the promise will remain unresolved forever. Make sure that after calling
 | 
			
		||||
     * this method there aren't any references to this object, or it could cause memory leaks.
 | 
			
		||||
     */
 | 
			
		||||
    cancel(): void {
 | 
			
		||||
        this.cancelPromise();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										54
									
								
								src/core/classes/promise.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								src/core/classes/promise.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,54 @@
 | 
			
		||||
// (C) Copyright 2015 Moodle Pty Ltd.
 | 
			
		||||
//
 | 
			
		||||
// Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
// you may not use this file except in compliance with the License.
 | 
			
		||||
// You may obtain a copy of the License at
 | 
			
		||||
//
 | 
			
		||||
//     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
//
 | 
			
		||||
// Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
// distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
// See the License for the specific language governing permissions and
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Base class to use for implementing custom Promises.
 | 
			
		||||
 */
 | 
			
		||||
export abstract class CorePromise<T = unknown> implements Promise<T> {
 | 
			
		||||
 | 
			
		||||
    protected nativePromise: Promise<T>;
 | 
			
		||||
 | 
			
		||||
    constructor(nativePromise: Promise<T>) {
 | 
			
		||||
        this.nativePromise = nativePromise;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    [Symbol.toStringTag]: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    then<TResult1 = T, TResult2 = never>(
 | 
			
		||||
        onFulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null,
 | 
			
		||||
        onRejected?: ((reason: Error) => TResult2 | PromiseLike<TResult2>) | undefined | null,
 | 
			
		||||
    ): Promise<TResult1 | TResult2> {
 | 
			
		||||
        return this.nativePromise.then(onFulfilled, onRejected);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    catch<TResult = never>(
 | 
			
		||||
        onRejected?: ((reason: Error) => TResult | PromiseLike<TResult>) | undefined | null,
 | 
			
		||||
    ): Promise<T | TResult> {
 | 
			
		||||
        return this.nativePromise.catch(onRejected);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    finally(onFinally?: (() => void) | null): Promise<T> {
 | 
			
		||||
        return this.nativePromise.finally(onFinally);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -12,10 +12,12 @@
 | 
			
		||||
// See the License for the specific language governing permissions and
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
import { CorePromise } from '@classes/promise';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Promise wrapper to expose result synchronously.
 | 
			
		||||
 */
 | 
			
		||||
export class CorePromisedValue<T = unknown> implements Promise<T> {
 | 
			
		||||
export class CorePromisedValue<T = unknown> extends CorePromise<T> {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Wrap an existing promise.
 | 
			
		||||
@ -33,20 +35,28 @@ export class CorePromisedValue<T = unknown> implements Promise<T> {
 | 
			
		||||
        return promisedValue;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private _resolvedValue?: T;
 | 
			
		||||
    private _rejectedReason?: Error;
 | 
			
		||||
    declare private promise: Promise<T>;
 | 
			
		||||
    declare private _resolve: (result: T) => void;
 | 
			
		||||
    declare private _reject: (error?: Error) => void;
 | 
			
		||||
    protected resolvedValue?: T;
 | 
			
		||||
    protected rejectedReason?: Error;
 | 
			
		||||
    protected resolvePromise!: (result: T) => void;
 | 
			
		||||
    protected rejectPromise!: (error?: Error) => void;
 | 
			
		||||
 | 
			
		||||
    constructor() {
 | 
			
		||||
        this.initPromise();
 | 
			
		||||
        let resolvePromise!: (result: T) => void;
 | 
			
		||||
        let rejectPromise!: (error?: Error) => void;
 | 
			
		||||
 | 
			
		||||
        const nativePromise = new Promise<T>((resolve, reject) => {
 | 
			
		||||
            resolvePromise = resolve;
 | 
			
		||||
            rejectPromise = reject;
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        super(nativePromise);
 | 
			
		||||
 | 
			
		||||
        this.resolvePromise = resolvePromise;
 | 
			
		||||
        this.rejectPromise = rejectPromise;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    [Symbol.toStringTag]: string;
 | 
			
		||||
 | 
			
		||||
    get value(): T | null {
 | 
			
		||||
        return this._resolvedValue ?? null;
 | 
			
		||||
        return this.resolvedValue ?? null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@ -55,7 +65,7 @@ export class CorePromisedValue<T = unknown> implements Promise<T> {
 | 
			
		||||
     * @return Whether the promise resolved successfuly.
 | 
			
		||||
     */
 | 
			
		||||
    isResolved(): this is { value: T } {
 | 
			
		||||
        return '_resolvedValue' in this;
 | 
			
		||||
        return 'resolvedValue' in this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@ -64,7 +74,7 @@ export class CorePromisedValue<T = unknown> implements Promise<T> {
 | 
			
		||||
     * @return Whether the promise was rejected.
 | 
			
		||||
     */
 | 
			
		||||
    isRejected(): boolean {
 | 
			
		||||
        return '_rejectedReason' in this;
 | 
			
		||||
        return 'rejectedReason' in this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@ -76,32 +86,6 @@ export class CorePromisedValue<T = unknown> implements Promise<T> {
 | 
			
		||||
        return this.isResolved() || this.isRejected();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    then<TResult1 = T, TResult2 = never>(
 | 
			
		||||
        onFulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null,
 | 
			
		||||
        onRejected?: ((reason: Error) => TResult2 | PromiseLike<TResult2>) | undefined | null,
 | 
			
		||||
    ): Promise<TResult1 | TResult2> {
 | 
			
		||||
        return this.promise.then(onFulfilled, onRejected);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    catch<TResult = never>(
 | 
			
		||||
        onRejected?: ((reason: Error) => TResult | PromiseLike<TResult>) | undefined | null,
 | 
			
		||||
    ): Promise<T | TResult> {
 | 
			
		||||
        return this.promise.catch(onRejected);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    finally(onFinally?: (() => void) | null): Promise<T> {
 | 
			
		||||
        return this.promise.finally(onFinally);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Resolve the promise.
 | 
			
		||||
     *
 | 
			
		||||
@ -109,13 +93,13 @@ export class CorePromisedValue<T = unknown> implements Promise<T> {
 | 
			
		||||
     */
 | 
			
		||||
    resolve(value: T): void {
 | 
			
		||||
        if (this.isSettled()) {
 | 
			
		||||
            delete this._rejectedReason;
 | 
			
		||||
            delete this.rejectedReason;
 | 
			
		||||
 | 
			
		||||
            this.initPromise();
 | 
			
		||||
            this.resetNativePromise();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this._resolvedValue = value;
 | 
			
		||||
        this._resolve(value);
 | 
			
		||||
        this.resolvedValue = value;
 | 
			
		||||
        this.resolvePromise(value);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@ -125,32 +109,32 @@ export class CorePromisedValue<T = unknown> implements Promise<T> {
 | 
			
		||||
     */
 | 
			
		||||
    reject(reason?: Error): void {
 | 
			
		||||
        if (this.isSettled()) {
 | 
			
		||||
            delete this._resolvedValue;
 | 
			
		||||
            delete this.resolvedValue;
 | 
			
		||||
 | 
			
		||||
            this.initPromise();
 | 
			
		||||
            this.resetNativePromise();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this._rejectedReason = reason;
 | 
			
		||||
        this._reject(reason);
 | 
			
		||||
        this.rejectedReason = reason;
 | 
			
		||||
        this.rejectPromise(reason);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Reset status and value.
 | 
			
		||||
     */
 | 
			
		||||
    reset(): void {
 | 
			
		||||
        delete this._resolvedValue;
 | 
			
		||||
        delete this._rejectedReason;
 | 
			
		||||
        delete this.resolvedValue;
 | 
			
		||||
        delete this.rejectedReason;
 | 
			
		||||
 | 
			
		||||
        this.initPromise();
 | 
			
		||||
        this.resetNativePromise();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Initialize the promise and the callbacks.
 | 
			
		||||
     * Reset native promise and callbacks.
 | 
			
		||||
     */
 | 
			
		||||
    private initPromise(): void {
 | 
			
		||||
        this.promise = new Promise((resolve, reject) => {
 | 
			
		||||
            this._resolve = resolve;
 | 
			
		||||
            this._reject = reject;
 | 
			
		||||
    protected resetNativePromise(): void {
 | 
			
		||||
        this.nativePromise = new Promise((resolve, reject) => {
 | 
			
		||||
            this.resolvePromise = resolve;
 | 
			
		||||
            this.rejectPromise = reject;
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -19,9 +19,10 @@ import { CoreUtils } from '@services/utils/utils';
 | 
			
		||||
import { CoreMath } from '@singletons/math';
 | 
			
		||||
import { CoreComponentsRegistry } from '@singletons/components-registry';
 | 
			
		||||
import { CoreFormatTextDirective } from './format-text';
 | 
			
		||||
import { CoreEventObserver, CoreSingleTimeEventObserver } from '@singletons/events';
 | 
			
		||||
import { CoreEventObserver } from '@singletons/events';
 | 
			
		||||
import { CoreLoadingComponent } from '@components/loading/loading';
 | 
			
		||||
import { CoreDomUtils } from '@services/utils/dom';
 | 
			
		||||
import { CoreCancellablePromise } from '@classes/cancellable-promise';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Directive to make an element fixed at the bottom collapsible when scrolling.
 | 
			
		||||
@ -48,7 +49,7 @@ export class CoreCollapsibleFooterDirective implements OnInit, OnDestroy {
 | 
			
		||||
    protected contentScrollListener?: EventListener;
 | 
			
		||||
    protected endContentScrollListener?: EventListener;
 | 
			
		||||
    protected resizeListener?: CoreEventObserver;
 | 
			
		||||
    protected domListener?: CoreSingleTimeEventObserver;
 | 
			
		||||
    protected domPromise?: CoreCancellablePromise<void>;
 | 
			
		||||
 | 
			
		||||
    constructor(el: ElementRef, protected ionContent: IonContent) {
 | 
			
		||||
        this.element = el.nativeElement;
 | 
			
		||||
@ -61,10 +62,9 @@ export class CoreCollapsibleFooterDirective implements OnInit, OnDestroy {
 | 
			
		||||
    async ngOnInit(): Promise<void> {
 | 
			
		||||
        // Only if not present or explicitly falsy it will be false.
 | 
			
		||||
        this.appearOnBottom = !CoreUtils.isFalseOrZero(this.appearOnBottom);
 | 
			
		||||
        this.domPromise = CoreDomUtils.waitToBeInDOM(this.element);
 | 
			
		||||
 | 
			
		||||
        this.domListener = CoreDomUtils.waitToBeInDOM(this.element);
 | 
			
		||||
        await this.domListener.promise;
 | 
			
		||||
 | 
			
		||||
        await this.domPromise;
 | 
			
		||||
        await this.waitLoadingsDone();
 | 
			
		||||
        await this.waitFormatTextsRendered(this.element);
 | 
			
		||||
 | 
			
		||||
@ -234,7 +234,7 @@ export class CoreCollapsibleFooterDirective implements OnInit, OnDestroy {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.resizeListener?.off();
 | 
			
		||||
        this.domListener?.off();
 | 
			
		||||
        this.domPromise?.cancel();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -13,12 +13,13 @@
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
import { Directive, ElementRef, Input, OnDestroy, OnInit } from '@angular/core';
 | 
			
		||||
import { CoreCancellablePromise } from '@classes/cancellable-promise';
 | 
			
		||||
import { CoreLoadingComponent } from '@components/loading/loading';
 | 
			
		||||
import { CoreDomUtils } from '@services/utils/dom';
 | 
			
		||||
import { CoreUtils } from '@services/utils/utils';
 | 
			
		||||
import { Translate } from '@singletons';
 | 
			
		||||
import { CoreComponentsRegistry } from '@singletons/components-registry';
 | 
			
		||||
import { CoreEventObserver, CoreSingleTimeEventObserver } from '@singletons/events';
 | 
			
		||||
import { CoreEventObserver } from '@singletons/events';
 | 
			
		||||
import { CoreFormatTextDirective } from './format-text';
 | 
			
		||||
 | 
			
		||||
const defaultMaxHeight = 80;
 | 
			
		||||
@ -49,7 +50,7 @@ export class CoreCollapsibleItemDirective implements OnInit, OnDestroy {
 | 
			
		||||
    protected maxHeight = defaultMaxHeight;
 | 
			
		||||
    protected expandedHeight = 0;
 | 
			
		||||
    protected resizeListener?: CoreEventObserver;
 | 
			
		||||
    protected domListener?: CoreSingleTimeEventObserver;
 | 
			
		||||
    protected domPromise?: CoreCancellablePromise<void>;
 | 
			
		||||
 | 
			
		||||
    constructor(el: ElementRef<HTMLElement>) {
 | 
			
		||||
        this.element = el.nativeElement;
 | 
			
		||||
@ -96,8 +97,9 @@ export class CoreCollapsibleItemDirective implements OnInit, OnDestroy {
 | 
			
		||||
     * @return Promise resolved when loadings are done.
 | 
			
		||||
     */
 | 
			
		||||
    protected async waitLoadingsDone(): Promise<void> {
 | 
			
		||||
        this.domListener = CoreDomUtils.waitToBeInDOM(this.element);
 | 
			
		||||
        await this.domListener.promise;
 | 
			
		||||
        this.domPromise = CoreDomUtils.waitToBeInDOM(this.element);
 | 
			
		||||
 | 
			
		||||
        await this.domPromise;
 | 
			
		||||
 | 
			
		||||
        const page = this.element.closest('.ion-page');
 | 
			
		||||
 | 
			
		||||
@ -242,7 +244,7 @@ export class CoreCollapsibleItemDirective implements OnInit, OnDestroy {
 | 
			
		||||
     */
 | 
			
		||||
    ngOnDestroy(): void {
 | 
			
		||||
        this.resizeListener?.off();
 | 
			
		||||
        this.domListener?.off();
 | 
			
		||||
        this.domPromise?.cancel();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -42,7 +42,7 @@ import { CoreFilterHelper } from '@features/filter/services/filter-helper';
 | 
			
		||||
import { CoreSubscriptions } from '@singletons/subscriptions';
 | 
			
		||||
import { CoreComponentsRegistry } from '@singletons/components-registry';
 | 
			
		||||
import { CoreCollapsibleItemDirective } from './collapsible-item';
 | 
			
		||||
import { CoreSingleTimeEventObserver } from '@singletons/events';
 | 
			
		||||
import { CoreCancellablePromise } from '@classes/cancellable-promise';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Directive to format text rendered. It renders the HTML and treats all links and media, using CoreLinkDirective
 | 
			
		||||
@ -90,7 +90,7 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy {
 | 
			
		||||
    protected element: HTMLElement;
 | 
			
		||||
    protected emptyText = '';
 | 
			
		||||
    protected contentSpan: HTMLElement;
 | 
			
		||||
    protected domListener?: CoreSingleTimeEventObserver;
 | 
			
		||||
    protected domPromise?: CoreCancellablePromise<void>;
 | 
			
		||||
 | 
			
		||||
    constructor(
 | 
			
		||||
        element: ElementRef,
 | 
			
		||||
@ -133,7 +133,7 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy {
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    ngOnDestroy(): void {
 | 
			
		||||
        this.domListener?.off();
 | 
			
		||||
        this.domPromise?.cancel();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@ -556,8 +556,9 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy {
 | 
			
		||||
     * @return The width of the element in pixels.
 | 
			
		||||
     */
 | 
			
		||||
    protected async getElementWidth(): Promise<number> {
 | 
			
		||||
        this.domListener = CoreDomUtils.waitToBeInDOM(this.element);
 | 
			
		||||
        await this.domListener.promise;
 | 
			
		||||
        this.domPromise = CoreDomUtils.waitToBeInDOM(this.element);
 | 
			
		||||
 | 
			
		||||
        await this.domPromise;
 | 
			
		||||
 | 
			
		||||
        let width = this.element.getBoundingClientRect().width;
 | 
			
		||||
        if (!width) {
 | 
			
		||||
@ -711,7 +712,7 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy {
 | 
			
		||||
                let width: string | number;
 | 
			
		||||
                let height: string | number;
 | 
			
		||||
 | 
			
		||||
                await CoreDomUtils.waitToBeInDOM(iframe, 5000).promise;
 | 
			
		||||
                await CoreDomUtils.waitToBeInDOM(iframe, 5000);
 | 
			
		||||
 | 
			
		||||
                if (iframe.width) {
 | 
			
		||||
                    width = iframe.width;
 | 
			
		||||
 | 
			
		||||
@ -13,8 +13,8 @@
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
import { Directive, ElementRef, EventEmitter, OnDestroy, OnInit, Output } from '@angular/core';
 | 
			
		||||
import { CoreCancellablePromise } from '@classes/cancellable-promise';
 | 
			
		||||
import { CoreDomUtils } from '@services/utils/dom';
 | 
			
		||||
import { CoreSingleTimeEventObserver } from '@singletons/events';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Directive to listen when an element becomes visible.
 | 
			
		||||
@ -27,7 +27,7 @@ export class CoreOnAppearDirective implements OnInit, OnDestroy {
 | 
			
		||||
    @Output() onAppear = new EventEmitter();
 | 
			
		||||
 | 
			
		||||
    private element: HTMLElement;
 | 
			
		||||
    protected domListener?: CoreSingleTimeEventObserver;
 | 
			
		||||
    protected domPromise?: CoreCancellablePromise<void>;
 | 
			
		||||
 | 
			
		||||
    constructor(element: ElementRef) {
 | 
			
		||||
        this.element = element.nativeElement;
 | 
			
		||||
@ -37,8 +37,9 @@ export class CoreOnAppearDirective implements OnInit, OnDestroy {
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    async ngOnInit(): Promise<void> {
 | 
			
		||||
        this.domListener = CoreDomUtils.waitToBeInDOM(this.element);
 | 
			
		||||
        await this.domListener.promise;
 | 
			
		||||
        this.domPromise = CoreDomUtils.waitToBeInDOM(this.element);
 | 
			
		||||
 | 
			
		||||
        await this.domPromise;
 | 
			
		||||
 | 
			
		||||
        this.onAppear.emit();
 | 
			
		||||
    }
 | 
			
		||||
@ -47,7 +48,7 @@ export class CoreOnAppearDirective implements OnInit, OnDestroy {
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    ngOnDestroy(): void {
 | 
			
		||||
        this.domListener?.off();
 | 
			
		||||
        this.domPromise?.cancel();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -36,11 +36,12 @@ import { CoreDomUtils } from '@services/utils/dom';
 | 
			
		||||
import { CoreUrlUtils } from '@services/utils/url';
 | 
			
		||||
import { CoreUtils } from '@services/utils/utils';
 | 
			
		||||
import { Platform, Translate } from '@singletons';
 | 
			
		||||
import { CoreEventFormActionData, CoreEventObserver, CoreEvents, CoreSingleTimeEventObserver } from '@singletons/events';
 | 
			
		||||
import { CoreEventFormActionData, CoreEventObserver, CoreEvents } from '@singletons/events';
 | 
			
		||||
import { CoreEditorOffline } from '../../services/editor-offline';
 | 
			
		||||
import { CoreComponentsRegistry } from '@singletons/components-registry';
 | 
			
		||||
import { CoreLoadingComponent } from '@components/loading/loading';
 | 
			
		||||
import { CoreScreen } from '@services/screen';
 | 
			
		||||
import { CoreCancellablePromise } from '@classes/cancellable-promise';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Component to display a rich text editor if enabled.
 | 
			
		||||
@ -105,7 +106,7 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn
 | 
			
		||||
    protected selectionChangeFunction?: () => void;
 | 
			
		||||
    protected languageChangedSubscription?: Subscription;
 | 
			
		||||
    protected resizeListener?: CoreEventObserver;
 | 
			
		||||
    protected domListener?: CoreSingleTimeEventObserver;
 | 
			
		||||
    protected domPromise?: CoreCancellablePromise<void>;
 | 
			
		||||
 | 
			
		||||
    rteEnabled = false;
 | 
			
		||||
    isPhone = false;
 | 
			
		||||
@ -288,8 +289,9 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn
 | 
			
		||||
     * @return Promise resolved when loadings are done.
 | 
			
		||||
     */
 | 
			
		||||
    protected async waitLoadingsDone(): Promise<void> {
 | 
			
		||||
        this.domListener = CoreDomUtils.waitToBeInDOM(this.element);
 | 
			
		||||
        await this.domListener.promise;
 | 
			
		||||
        this.domPromise = CoreDomUtils.waitToBeInDOM(this.element);
 | 
			
		||||
 | 
			
		||||
        await this.domPromise;
 | 
			
		||||
 | 
			
		||||
        const page = this.element.closest('.ion-page');
 | 
			
		||||
 | 
			
		||||
@ -829,7 +831,7 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn
 | 
			
		||||
 | 
			
		||||
        const length = await this.toolbarSlides.length();
 | 
			
		||||
 | 
			
		||||
        await CoreDomUtils.waitToBeInDOM(this.toolbar.nativeElement, 5000).promise;
 | 
			
		||||
        await CoreDomUtils.waitToBeInDOM(this.toolbar.nativeElement, 5000);
 | 
			
		||||
 | 
			
		||||
        const width = this.toolbar.nativeElement.getBoundingClientRect().width;
 | 
			
		||||
 | 
			
		||||
@ -1089,7 +1091,7 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn
 | 
			
		||||
        this.keyboardObserver?.off();
 | 
			
		||||
        this.labelObserver?.disconnect();
 | 
			
		||||
        this.resizeListener?.off();
 | 
			
		||||
        this.domListener?.off();
 | 
			
		||||
        this.domPromise?.cancel();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -53,7 +53,8 @@ import { NavigationStart } from '@angular/router';
 | 
			
		||||
import { filter } from 'rxjs/operators';
 | 
			
		||||
import { Subscription } from 'rxjs';
 | 
			
		||||
import { CoreComponentsRegistry } from '@singletons/components-registry';
 | 
			
		||||
import { CoreEventObserver, CoreSingleTimeEventObserver } from '@singletons/events';
 | 
			
		||||
import { CoreEventObserver } from '@singletons/events';
 | 
			
		||||
import { CoreCancellablePromise } from '@classes/cancellable-promise';
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * "Utils" service with helper functions for UI, DOM elements and HTML code.
 | 
			
		||||
@ -97,51 +98,46 @@ export class CoreDomUtilsProvider {
 | 
			
		||||
     *
 | 
			
		||||
     * @param element Element to wait.
 | 
			
		||||
     * @param timeout If defined, timeout to wait before rejecting the promise.
 | 
			
		||||
     * @return Promise CoreSingleTimeEventObserver with a promise.
 | 
			
		||||
     * @return Cancellable promise.
 | 
			
		||||
     */
 | 
			
		||||
    waitToBeInDOM(
 | 
			
		||||
        element: Element,
 | 
			
		||||
        timeout?: number,
 | 
			
		||||
    ): CoreSingleTimeEventObserver {
 | 
			
		||||
        let root = element.getRootNode({ composed: true });
 | 
			
		||||
    waitToBeInDOM(element: Element, timeout?: number): CoreCancellablePromise<void> {
 | 
			
		||||
        const root = element.getRootNode({ composed: true });
 | 
			
		||||
 | 
			
		||||
        if (root === document) {
 | 
			
		||||
            // Already in DOM.
 | 
			
		||||
            return {
 | 
			
		||||
                off: (): void => {
 | 
			
		||||
                    // Nothing to do here.
 | 
			
		||||
                },
 | 
			
		||||
                promise: Promise.resolve(),
 | 
			
		||||
            };
 | 
			
		||||
            return CoreCancellablePromise.resolve();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let observer: MutationObserver | undefined;
 | 
			
		||||
        let observer: MutationObserver;
 | 
			
		||||
        let observerTimeout: number | undefined;
 | 
			
		||||
        if (timeout) {
 | 
			
		||||
            observerTimeout = window.setTimeout(() => {
 | 
			
		||||
                observer?.disconnect();
 | 
			
		||||
                throw new Error('Waiting for DOM timeout reached');
 | 
			
		||||
            }, timeout);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return {
 | 
			
		||||
            off: (): void => {
 | 
			
		||||
                observer?.disconnect();
 | 
			
		||||
                clearTimeout(observerTimeout);
 | 
			
		||||
            },
 | 
			
		||||
            promise: new Promise((resolve) => {
 | 
			
		||||
        return new CoreCancellablePromise<void>(
 | 
			
		||||
            (resolve, reject) => {
 | 
			
		||||
                observer = new MutationObserver(() => {
 | 
			
		||||
                    root = element.getRootNode({ composed: true });
 | 
			
		||||
                    const root = element.getRootNode({ composed: true });
 | 
			
		||||
 | 
			
		||||
                    if (root === document) {
 | 
			
		||||
                        observer?.disconnect();
 | 
			
		||||
                        clearTimeout(observerTimeout);
 | 
			
		||||
                        observerTimeout && clearTimeout(observerTimeout);
 | 
			
		||||
                        resolve();
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                if (timeout) {
 | 
			
		||||
                    observerTimeout = window.setTimeout(() => {
 | 
			
		||||
                        observer?.disconnect();
 | 
			
		||||
 | 
			
		||||
                        reject(new Error('Waiting for DOM timeout reached'));
 | 
			
		||||
                    }, timeout);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                observer.observe(document.body, { subtree: true, childList: true });
 | 
			
		||||
            }),
 | 
			
		||||
        };
 | 
			
		||||
            },
 | 
			
		||||
            () => {
 | 
			
		||||
                observer?.disconnect();
 | 
			
		||||
                observerTimeout && clearTimeout(observerTimeout);
 | 
			
		||||
            },
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 | 
			
		||||
@ -31,21 +31,6 @@ export interface CoreEventObserver {
 | 
			
		||||
    off: () => void;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Observer instance to stop listening to an observer.
 | 
			
		||||
 */
 | 
			
		||||
export interface CoreSingleTimeEventObserver {
 | 
			
		||||
    /**
 | 
			
		||||
     * Stop the observer.
 | 
			
		||||
     */
 | 
			
		||||
    off: () => void;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Promise Resolved when event is done (first time).
 | 
			
		||||
     */
 | 
			
		||||
    promise: Promise<void>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Event payloads.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user