forked from EVOgeek/Vmeda.Online
		
	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
 | // See the License for the specific language governing permissions and
 | ||||||
| // limitations under the License.
 | // limitations under the License.
 | ||||||
| 
 | 
 | ||||||
|  | import { CorePromise } from '@classes/promise'; | ||||||
|  | 
 | ||||||
| /** | /** | ||||||
|  * Promise wrapper to expose result synchronously. |  * 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. |      * Wrap an existing promise. | ||||||
| @ -33,20 +35,28 @@ export class CorePromisedValue<T = unknown> implements Promise<T> { | |||||||
|         return promisedValue; |         return promisedValue; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private _resolvedValue?: T; |     protected resolvedValue?: T; | ||||||
|     private _rejectedReason?: Error; |     protected rejectedReason?: Error; | ||||||
|     declare private promise: Promise<T>; |     protected resolvePromise!: (result: T) => void; | ||||||
|     declare private _resolve: (result: T) => void; |     protected rejectPromise!: (error?: Error) => void; | ||||||
|     declare private _reject: (error?: Error) => void; |  | ||||||
| 
 | 
 | ||||||
|     constructor() { |     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 { |     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. |      * @return Whether the promise resolved successfuly. | ||||||
|      */ |      */ | ||||||
|     isResolved(): this is { value: T } { |     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. |      * @return Whether the promise was rejected. | ||||||
|      */ |      */ | ||||||
|     isRejected(): boolean { |     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(); |         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. |      * Resolve the promise. | ||||||
|      * |      * | ||||||
| @ -109,13 +93,13 @@ export class CorePromisedValue<T = unknown> implements Promise<T> { | |||||||
|      */ |      */ | ||||||
|     resolve(value: T): void { |     resolve(value: T): void { | ||||||
|         if (this.isSettled()) { |         if (this.isSettled()) { | ||||||
|             delete this._rejectedReason; |             delete this.rejectedReason; | ||||||
| 
 | 
 | ||||||
|             this.initPromise(); |             this.resetNativePromise(); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         this._resolvedValue = value; |         this.resolvedValue = value; | ||||||
|         this._resolve(value); |         this.resolvePromise(value); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @ -125,32 +109,32 @@ export class CorePromisedValue<T = unknown> implements Promise<T> { | |||||||
|      */ |      */ | ||||||
|     reject(reason?: Error): void { |     reject(reason?: Error): void { | ||||||
|         if (this.isSettled()) { |         if (this.isSettled()) { | ||||||
|             delete this._resolvedValue; |             delete this.resolvedValue; | ||||||
| 
 | 
 | ||||||
|             this.initPromise(); |             this.resetNativePromise(); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         this._rejectedReason = reason; |         this.rejectedReason = reason; | ||||||
|         this._reject(reason); |         this.rejectPromise(reason); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Reset status and value. |      * Reset status and value. | ||||||
|      */ |      */ | ||||||
|     reset(): void { |     reset(): void { | ||||||
|         delete this._resolvedValue; |         delete this.resolvedValue; | ||||||
|         delete this._rejectedReason; |         delete this.rejectedReason; | ||||||
| 
 | 
 | ||||||
|         this.initPromise(); |         this.resetNativePromise(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Initialize the promise and the callbacks. |      * Reset native promise and callbacks. | ||||||
|      */ |      */ | ||||||
|     private initPromise(): void { |     protected resetNativePromise(): void { | ||||||
|         this.promise = new Promise((resolve, reject) => { |         this.nativePromise = new Promise((resolve, reject) => { | ||||||
|             this._resolve = resolve; |             this.resolvePromise = resolve; | ||||||
|             this._reject = reject; |             this.rejectPromise = reject; | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -19,9 +19,10 @@ import { CoreUtils } from '@services/utils/utils'; | |||||||
| import { CoreMath } from '@singletons/math'; | import { CoreMath } from '@singletons/math'; | ||||||
| import { CoreComponentsRegistry } from '@singletons/components-registry'; | import { CoreComponentsRegistry } from '@singletons/components-registry'; | ||||||
| import { CoreFormatTextDirective } from './format-text'; | import { CoreFormatTextDirective } from './format-text'; | ||||||
| import { CoreEventObserver, CoreSingleTimeEventObserver } from '@singletons/events'; | import { CoreEventObserver } from '@singletons/events'; | ||||||
| import { CoreLoadingComponent } from '@components/loading/loading'; | import { CoreLoadingComponent } from '@components/loading/loading'; | ||||||
| import { CoreDomUtils } from '@services/utils/dom'; | import { CoreDomUtils } from '@services/utils/dom'; | ||||||
|  | import { CoreCancellablePromise } from '@classes/cancellable-promise'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Directive to make an element fixed at the bottom collapsible when scrolling. |  * 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 contentScrollListener?: EventListener; | ||||||
|     protected endContentScrollListener?: EventListener; |     protected endContentScrollListener?: EventListener; | ||||||
|     protected resizeListener?: CoreEventObserver; |     protected resizeListener?: CoreEventObserver; | ||||||
|     protected domListener?: CoreSingleTimeEventObserver; |     protected domPromise?: CoreCancellablePromise<void>; | ||||||
| 
 | 
 | ||||||
|     constructor(el: ElementRef, protected ionContent: IonContent) { |     constructor(el: ElementRef, protected ionContent: IonContent) { | ||||||
|         this.element = el.nativeElement; |         this.element = el.nativeElement; | ||||||
| @ -61,10 +62,9 @@ export class CoreCollapsibleFooterDirective implements OnInit, OnDestroy { | |||||||
|     async ngOnInit(): Promise<void> { |     async ngOnInit(): Promise<void> { | ||||||
|         // Only if not present or explicitly falsy it will be false.
 |         // Only if not present or explicitly falsy it will be false.
 | ||||||
|         this.appearOnBottom = !CoreUtils.isFalseOrZero(this.appearOnBottom); |         this.appearOnBottom = !CoreUtils.isFalseOrZero(this.appearOnBottom); | ||||||
|  |         this.domPromise = CoreDomUtils.waitToBeInDOM(this.element); | ||||||
| 
 | 
 | ||||||
|         this.domListener = CoreDomUtils.waitToBeInDOM(this.element); |         await this.domPromise; | ||||||
|         await this.domListener.promise; |  | ||||||
| 
 |  | ||||||
|         await this.waitLoadingsDone(); |         await this.waitLoadingsDone(); | ||||||
|         await this.waitFormatTextsRendered(this.element); |         await this.waitFormatTextsRendered(this.element); | ||||||
| 
 | 
 | ||||||
| @ -234,7 +234,7 @@ export class CoreCollapsibleFooterDirective implements OnInit, OnDestroy { | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         this.resizeListener?.off(); |         this.resizeListener?.off(); | ||||||
|         this.domListener?.off(); |         this.domPromise?.cancel(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  | |||||||
| @ -13,12 +13,13 @@ | |||||||
| // limitations under the License.
 | // limitations under the License.
 | ||||||
| 
 | 
 | ||||||
| import { Directive, ElementRef, Input, OnDestroy, OnInit } from '@angular/core'; | import { Directive, ElementRef, Input, OnDestroy, OnInit } from '@angular/core'; | ||||||
|  | import { CoreCancellablePromise } from '@classes/cancellable-promise'; | ||||||
| import { CoreLoadingComponent } from '@components/loading/loading'; | import { CoreLoadingComponent } from '@components/loading/loading'; | ||||||
| import { CoreDomUtils } from '@services/utils/dom'; | import { CoreDomUtils } from '@services/utils/dom'; | ||||||
| import { CoreUtils } from '@services/utils/utils'; | import { CoreUtils } from '@services/utils/utils'; | ||||||
| import { Translate } from '@singletons'; | import { Translate } from '@singletons'; | ||||||
| import { CoreComponentsRegistry } from '@singletons/components-registry'; | import { CoreComponentsRegistry } from '@singletons/components-registry'; | ||||||
| import { CoreEventObserver, CoreSingleTimeEventObserver } from '@singletons/events'; | import { CoreEventObserver } from '@singletons/events'; | ||||||
| import { CoreFormatTextDirective } from './format-text'; | import { CoreFormatTextDirective } from './format-text'; | ||||||
| 
 | 
 | ||||||
| const defaultMaxHeight = 80; | const defaultMaxHeight = 80; | ||||||
| @ -49,7 +50,7 @@ export class CoreCollapsibleItemDirective implements OnInit, OnDestroy { | |||||||
|     protected maxHeight = defaultMaxHeight; |     protected maxHeight = defaultMaxHeight; | ||||||
|     protected expandedHeight = 0; |     protected expandedHeight = 0; | ||||||
|     protected resizeListener?: CoreEventObserver; |     protected resizeListener?: CoreEventObserver; | ||||||
|     protected domListener?: CoreSingleTimeEventObserver; |     protected domPromise?: CoreCancellablePromise<void>; | ||||||
| 
 | 
 | ||||||
|     constructor(el: ElementRef<HTMLElement>) { |     constructor(el: ElementRef<HTMLElement>) { | ||||||
|         this.element = el.nativeElement; |         this.element = el.nativeElement; | ||||||
| @ -96,8 +97,9 @@ export class CoreCollapsibleItemDirective implements OnInit, OnDestroy { | |||||||
|      * @return Promise resolved when loadings are done. |      * @return Promise resolved when loadings are done. | ||||||
|      */ |      */ | ||||||
|     protected async waitLoadingsDone(): Promise<void> { |     protected async waitLoadingsDone(): Promise<void> { | ||||||
|         this.domListener = CoreDomUtils.waitToBeInDOM(this.element); |         this.domPromise = CoreDomUtils.waitToBeInDOM(this.element); | ||||||
|         await this.domListener.promise; | 
 | ||||||
|  |         await this.domPromise; | ||||||
| 
 | 
 | ||||||
|         const page = this.element.closest('.ion-page'); |         const page = this.element.closest('.ion-page'); | ||||||
| 
 | 
 | ||||||
| @ -242,7 +244,7 @@ export class CoreCollapsibleItemDirective implements OnInit, OnDestroy { | |||||||
|      */ |      */ | ||||||
|     ngOnDestroy(): void { |     ngOnDestroy(): void { | ||||||
|         this.resizeListener?.off(); |         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 { CoreSubscriptions } from '@singletons/subscriptions'; | ||||||
| import { CoreComponentsRegistry } from '@singletons/components-registry'; | import { CoreComponentsRegistry } from '@singletons/components-registry'; | ||||||
| import { CoreCollapsibleItemDirective } from './collapsible-item'; | 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 |  * 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 element: HTMLElement; | ||||||
|     protected emptyText = ''; |     protected emptyText = ''; | ||||||
|     protected contentSpan: HTMLElement; |     protected contentSpan: HTMLElement; | ||||||
|     protected domListener?: CoreSingleTimeEventObserver; |     protected domPromise?: CoreCancellablePromise<void>; | ||||||
| 
 | 
 | ||||||
|     constructor( |     constructor( | ||||||
|         element: ElementRef, |         element: ElementRef, | ||||||
| @ -133,7 +133,7 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy { | |||||||
|      * @inheritdoc |      * @inheritdoc | ||||||
|      */ |      */ | ||||||
|     ngOnDestroy(): void { |     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. |      * @return The width of the element in pixels. | ||||||
|      */ |      */ | ||||||
|     protected async getElementWidth(): Promise<number> { |     protected async getElementWidth(): Promise<number> { | ||||||
|         this.domListener = CoreDomUtils.waitToBeInDOM(this.element); |         this.domPromise = CoreDomUtils.waitToBeInDOM(this.element); | ||||||
|         await this.domListener.promise; | 
 | ||||||
|  |         await this.domPromise; | ||||||
| 
 | 
 | ||||||
|         let width = this.element.getBoundingClientRect().width; |         let width = this.element.getBoundingClientRect().width; | ||||||
|         if (!width) { |         if (!width) { | ||||||
| @ -711,7 +712,7 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy { | |||||||
|                 let width: string | number; |                 let width: string | number; | ||||||
|                 let height: string | number; |                 let height: string | number; | ||||||
| 
 | 
 | ||||||
|                 await CoreDomUtils.waitToBeInDOM(iframe, 5000).promise; |                 await CoreDomUtils.waitToBeInDOM(iframe, 5000); | ||||||
| 
 | 
 | ||||||
|                 if (iframe.width) { |                 if (iframe.width) { | ||||||
|                     width = iframe.width; |                     width = iframe.width; | ||||||
|  | |||||||
| @ -13,8 +13,8 @@ | |||||||
| // limitations under the License.
 | // limitations under the License.
 | ||||||
| 
 | 
 | ||||||
| import { Directive, ElementRef, EventEmitter, OnDestroy, OnInit, Output } from '@angular/core'; | import { Directive, ElementRef, EventEmitter, OnDestroy, OnInit, Output } from '@angular/core'; | ||||||
|  | import { CoreCancellablePromise } from '@classes/cancellable-promise'; | ||||||
| import { CoreDomUtils } from '@services/utils/dom'; | import { CoreDomUtils } from '@services/utils/dom'; | ||||||
| import { CoreSingleTimeEventObserver } from '@singletons/events'; |  | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Directive to listen when an element becomes visible. |  * Directive to listen when an element becomes visible. | ||||||
| @ -27,7 +27,7 @@ export class CoreOnAppearDirective implements OnInit, OnDestroy { | |||||||
|     @Output() onAppear = new EventEmitter(); |     @Output() onAppear = new EventEmitter(); | ||||||
| 
 | 
 | ||||||
|     private element: HTMLElement; |     private element: HTMLElement; | ||||||
|     protected domListener?: CoreSingleTimeEventObserver; |     protected domPromise?: CoreCancellablePromise<void>; | ||||||
| 
 | 
 | ||||||
|     constructor(element: ElementRef) { |     constructor(element: ElementRef) { | ||||||
|         this.element = element.nativeElement; |         this.element = element.nativeElement; | ||||||
| @ -37,8 +37,9 @@ export class CoreOnAppearDirective implements OnInit, OnDestroy { | |||||||
|      * @inheritdoc |      * @inheritdoc | ||||||
|      */ |      */ | ||||||
|     async ngOnInit(): Promise<void> { |     async ngOnInit(): Promise<void> { | ||||||
|         this.domListener = CoreDomUtils.waitToBeInDOM(this.element); |         this.domPromise = CoreDomUtils.waitToBeInDOM(this.element); | ||||||
|         await this.domListener.promise; | 
 | ||||||
|  |         await this.domPromise; | ||||||
| 
 | 
 | ||||||
|         this.onAppear.emit(); |         this.onAppear.emit(); | ||||||
|     } |     } | ||||||
| @ -47,7 +48,7 @@ export class CoreOnAppearDirective implements OnInit, OnDestroy { | |||||||
|      * @inheritdoc |      * @inheritdoc | ||||||
|      */ |      */ | ||||||
|     ngOnDestroy(): void { |     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 { CoreUrlUtils } from '@services/utils/url'; | ||||||
| import { CoreUtils } from '@services/utils/utils'; | import { CoreUtils } from '@services/utils/utils'; | ||||||
| import { Platform, Translate } from '@singletons'; | 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 { CoreEditorOffline } from '../../services/editor-offline'; | ||||||
| import { CoreComponentsRegistry } from '@singletons/components-registry'; | import { CoreComponentsRegistry } from '@singletons/components-registry'; | ||||||
| import { CoreLoadingComponent } from '@components/loading/loading'; | import { CoreLoadingComponent } from '@components/loading/loading'; | ||||||
| import { CoreScreen } from '@services/screen'; | import { CoreScreen } from '@services/screen'; | ||||||
|  | import { CoreCancellablePromise } from '@classes/cancellable-promise'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Component to display a rich text editor if enabled. |  * Component to display a rich text editor if enabled. | ||||||
| @ -105,7 +106,7 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn | |||||||
|     protected selectionChangeFunction?: () => void; |     protected selectionChangeFunction?: () => void; | ||||||
|     protected languageChangedSubscription?: Subscription; |     protected languageChangedSubscription?: Subscription; | ||||||
|     protected resizeListener?: CoreEventObserver; |     protected resizeListener?: CoreEventObserver; | ||||||
|     protected domListener?: CoreSingleTimeEventObserver; |     protected domPromise?: CoreCancellablePromise<void>; | ||||||
| 
 | 
 | ||||||
|     rteEnabled = false; |     rteEnabled = false; | ||||||
|     isPhone = false; |     isPhone = false; | ||||||
| @ -288,8 +289,9 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn | |||||||
|      * @return Promise resolved when loadings are done. |      * @return Promise resolved when loadings are done. | ||||||
|      */ |      */ | ||||||
|     protected async waitLoadingsDone(): Promise<void> { |     protected async waitLoadingsDone(): Promise<void> { | ||||||
|         this.domListener = CoreDomUtils.waitToBeInDOM(this.element); |         this.domPromise = CoreDomUtils.waitToBeInDOM(this.element); | ||||||
|         await this.domListener.promise; | 
 | ||||||
|  |         await this.domPromise; | ||||||
| 
 | 
 | ||||||
|         const page = this.element.closest('.ion-page'); |         const page = this.element.closest('.ion-page'); | ||||||
| 
 | 
 | ||||||
| @ -829,7 +831,7 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn | |||||||
| 
 | 
 | ||||||
|         const length = await this.toolbarSlides.length(); |         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; |         const width = this.toolbar.nativeElement.getBoundingClientRect().width; | ||||||
| 
 | 
 | ||||||
| @ -1089,7 +1091,7 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn | |||||||
|         this.keyboardObserver?.off(); |         this.keyboardObserver?.off(); | ||||||
|         this.labelObserver?.disconnect(); |         this.labelObserver?.disconnect(); | ||||||
|         this.resizeListener?.off(); |         this.resizeListener?.off(); | ||||||
|         this.domListener?.off(); |         this.domPromise?.cancel(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  | |||||||
| @ -53,7 +53,8 @@ import { NavigationStart } from '@angular/router'; | |||||||
| import { filter } from 'rxjs/operators'; | import { filter } from 'rxjs/operators'; | ||||||
| import { Subscription } from 'rxjs'; | import { Subscription } from 'rxjs'; | ||||||
| import { CoreComponentsRegistry } from '@singletons/components-registry'; | 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. |  * "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 element Element to wait. | ||||||
|      * @param timeout If defined, timeout to wait before rejecting the promise. |      * @param timeout If defined, timeout to wait before rejecting the promise. | ||||||
|      * @return Promise CoreSingleTimeEventObserver with a promise. |      * @return Cancellable promise. | ||||||
|      */ |      */ | ||||||
|     waitToBeInDOM( |     waitToBeInDOM(element: Element, timeout?: number): CoreCancellablePromise<void> { | ||||||
|         element: Element, |         const root = element.getRootNode({ composed: true }); | ||||||
|         timeout?: number, |  | ||||||
|     ): CoreSingleTimeEventObserver { |  | ||||||
|         let root = element.getRootNode({ composed: true }); |  | ||||||
| 
 | 
 | ||||||
|         if (root === document) { |         if (root === document) { | ||||||
|             // Already in DOM.
 |             // Already in DOM.
 | ||||||
|             return { |             return CoreCancellablePromise.resolve(); | ||||||
|                 off: (): void => { |  | ||||||
|                     // Nothing to do here.
 |  | ||||||
|                 }, |  | ||||||
|                 promise: Promise.resolve(), |  | ||||||
|             }; |  | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         let observer: MutationObserver | undefined; |         let observer: MutationObserver; | ||||||
|         let observerTimeout: number | undefined; |         let observerTimeout: number | undefined; | ||||||
|         if (timeout) { |  | ||||||
|             observerTimeout = window.setTimeout(() => { |  | ||||||
|                 observer?.disconnect(); |  | ||||||
|                 throw new Error('Waiting for DOM timeout reached'); |  | ||||||
|             }, timeout); |  | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|         return { |         return new CoreCancellablePromise<void>( | ||||||
|             off: (): void => { |             (resolve, reject) => { | ||||||
|                 observer?.disconnect(); |  | ||||||
|                 clearTimeout(observerTimeout); |  | ||||||
|             }, |  | ||||||
|             promise: new Promise((resolve) => { |  | ||||||
|                 observer = new MutationObserver(() => { |                 observer = new MutationObserver(() => { | ||||||
|                     root = element.getRootNode({ composed: true }); |                     const root = element.getRootNode({ composed: true }); | ||||||
|  | 
 | ||||||
|                     if (root === document) { |                     if (root === document) { | ||||||
|                         observer?.disconnect(); |                         observer?.disconnect(); | ||||||
|                         clearTimeout(observerTimeout); |                         observerTimeout && clearTimeout(observerTimeout); | ||||||
|                         resolve(); |                         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.observe(document.body, { subtree: true, childList: true }); | ||||||
|             }), |             }, | ||||||
|         }; |             () => { | ||||||
|  |                 observer?.disconnect(); | ||||||
|  |                 observerTimeout && clearTimeout(observerTimeout); | ||||||
|  |             }, | ||||||
|  |         ); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|  | |||||||
| @ -31,21 +31,6 @@ export interface CoreEventObserver { | |||||||
|     off: () => void; |     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. |  * Event payloads. | ||||||
|  */ |  */ | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user