Merge pull request #3181 from NoelDeMartin/MOBILE-3833
MOBILE-3833 core: Implement cancellable promisemain
commit
606f43b526
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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…
Reference in New Issue