diff --git a/src/addons/filter/mathjaxloader/services/handlers/mathjaxloader.ts b/src/addons/filter/mathjaxloader/services/handlers/mathjaxloader.ts index 7ca996ae7..caa72b2d2 100644 --- a/src/addons/filter/mathjaxloader/services/handlers/mathjaxloader.ts +++ b/src/addons/filter/mathjaxloader/services/handlers/mathjaxloader.ts @@ -23,6 +23,7 @@ import { CoreUtils } from '@services/utils/utils'; import { CoreEvents } from '@singletons/events'; import { CoreSite } from '@classes/sites/site'; import { makeSingleton } from '@singletons'; +import { CoreWait } from '@singletons/wait'; /** * Handler to support the MathJax filter. @@ -321,7 +322,7 @@ export class AddonFilterMathJaxLoaderHandlerService extends CoreFilterDefaultHan return; } - await CoreUtils.wait(250); + await CoreWait.wait(250); await CoreUtils.ignoreErrors(this.waitForReady(retries + 1)); } diff --git a/src/addons/messages/pages/discussion/discussion.ts b/src/addons/messages/pages/discussion/discussion.ts index 6afb3bb64..78df7f2e3 100644 --- a/src/addons/messages/pages/discussion/discussion.ts +++ b/src/addons/messages/pages/discussion/discussion.ts @@ -46,6 +46,7 @@ import { CoreConstants } from '@/core/constants'; import { CoreDom } from '@singletons/dom'; import { CoreKeyboard } from '@singletons/keyboard'; import { CoreText } from '@singletons/text'; +import { CoreWait } from '@singletons/wait'; /** * Page that displays a message discussion page. @@ -884,7 +885,7 @@ export class AddonMessagesDiscussionPage implements OnInit, OnDestroy, AfterView return; } - await CoreUtils.wait(400); + await CoreWait.wait(400); await CoreUtils.ignoreErrors(this.waitForFetch()); } @@ -1072,7 +1073,7 @@ export class AddonMessagesDiscussionPage implements OnInit, OnDestroy, AfterView this.setNewMessagesBadge(0); // Leave time for the view to be rendered. - await CoreUtils.nextTicks(5); + await CoreWait.nextTicks(5); if (!this.viewDestroyed && this.content) { this.content.scrollToBottom(0); diff --git a/src/addons/messages/services/messages-sync.ts b/src/addons/messages/services/messages-sync.ts index ef66db791..73b09de33 100644 --- a/src/addons/messages/services/messages-sync.ts +++ b/src/addons/messages/services/messages-sync.ts @@ -32,6 +32,7 @@ import { CoreUser } from '@features/user/services/user'; import { CoreError } from '@classes/errors/error'; import { CoreTextErrorObject, CoreTextUtils } from '@services/utils/text'; import { CoreSiteWSPreSets } from '@classes/sites/authenticated-site'; +import { CoreWait } from '@singletons/wait'; /** * Service to sync messages. @@ -252,7 +253,7 @@ export class AddonMessagesSyncProvider extends CoreSyncBaseProvider { switch (this.action) { case 'editsubmission': - await CoreUtils.waitFor(() => !!this.activityComponent?.submissionComponent, { timeout: 5000 }); + await CoreWait.waitFor(() => !!this.activityComponent?.submissionComponent, { timeout: 5000 }); await this.activityComponent?.submissionComponent?.goToEdit(); break; diff --git a/src/addons/mod/chat/pages/chat/chat.ts b/src/addons/mod/chat/pages/chat/chat.ts index cbffd3a3a..f1d770852 100644 --- a/src/addons/mod/chat/pages/chat/chat.ts +++ b/src/addons/mod/chat/pages/chat/chat.ts @@ -30,6 +30,7 @@ import { AddonModChatFormattedMessage, AddonModChatHelper } from '../../services import { CoreTime } from '@singletons/time'; import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; import { CoreKeyboard } from '@singletons/keyboard'; +import { CoreWait } from '@singletons/wait'; /** * Page that displays a chat session. @@ -358,7 +359,7 @@ export class AddonModChatChatPage implements OnInit, OnDestroy, CanLeave { */ async scrollToBottom(): Promise { // Need a timeout to leave time to the view to be rendered. - await CoreUtils.nextTick(); + await CoreWait.nextTick(); if (!this.viewDestroyed) { this.content?.scrollToBottom(); } diff --git a/src/addons/mod/quiz/pages/player/player.ts b/src/addons/mod/quiz/pages/player/player.ts index 6dd4dced4..c67450887 100644 --- a/src/addons/mod/quiz/pages/player/player.ts +++ b/src/addons/mod/quiz/pages/player/player.ts @@ -54,6 +54,7 @@ import { CoreDirectivesRegistry } from '@singletons/directives-registry'; import { CoreWSError } from '@classes/errors/wserror'; import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; import { ADDON_MOD_QUIZ_ATTEMPT_FINISHED_EVENT, AddonModQuizAttemptStates, ADDON_MOD_QUIZ_COMPONENT } from '../../constants'; +import { CoreWait } from '@singletons/wait'; /** * Page that allows attempting a quiz. @@ -831,7 +832,7 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy, CanLeave { * @param slot Slot of the question to scroll to. */ protected async scrollToQuestion(slot: number): Promise { - await CoreUtils.nextTick(); + await CoreWait.nextTick(); await CoreDirectivesRegistry.waitDirectivesReady(this.elementRef.nativeElement, 'core-question'); await CoreDom.scrollToElement( this.elementRef.nativeElement, diff --git a/src/addons/mod/scorm/components/index/index.ts b/src/addons/mod/scorm/components/index/index.ts index 4c8e7ee63..eb9febdb4 100644 --- a/src/addons/mod/scorm/components/index/index.ts +++ b/src/addons/mod/scorm/components/index/index.ts @@ -49,6 +49,7 @@ import { ADDON_MOD_SCORM_DATA_AUTO_SYNCED, ADDON_MOD_SCORM_PAGE_NAME, } from '../../constants'; +import { CoreWait } from '@singletons/wait'; /** * Component that displays a SCORM entry page. @@ -616,7 +617,7 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom if (CoreSync.isBlocked(ADDON_MOD_SCORM_COMPONENT, this.scorm.id) && retries < 5) { // Sync is currently blocked, this can happen when SCORM player is left. Retry in a bit. - await CoreUtils.wait(400); + await CoreWait.wait(400); return this.sync(retries + 1); } diff --git a/src/addons/mod/scorm/pages/player/player.ts b/src/addons/mod/scorm/pages/player/player.ts index be6162944..c9d4f6aa5 100644 --- a/src/addons/mod/scorm/pages/player/player.ts +++ b/src/addons/mod/scorm/pages/player/player.ts @@ -43,6 +43,7 @@ import { ADDON_MOD_SCORM_LAUNCH_PREV_SCO_EVENT, ADDON_MOD_SCORM_UPDATE_TOC_EVENT, } from '../../constants'; +import { CoreWait } from '@singletons/wait'; /** * Page that allows playing a SCORM. @@ -436,7 +437,7 @@ export class AddonModScormPlayerPage implements OnInit, OnDestroy { // Changing SCO. First unload the existing SCO to make sure the callback to send the data has been called. this.src = ''; - await CoreUtils.nextTick(); + await CoreWait.nextTick(); // Load the SCO in the existing model. this.dataModel.loadSco(sco.id); diff --git a/src/addons/qtype/ddwtos/classes/ddwtos.ts b/src/addons/qtype/ddwtos/classes/ddwtos.ts index 2133ecb26..338217d2e 100644 --- a/src/addons/qtype/ddwtos/classes/ddwtos.ts +++ b/src/addons/qtype/ddwtos/classes/ddwtos.ts @@ -14,12 +14,12 @@ import { CoreFormatTextDirective } from '@directives/format-text'; import { CoreTextUtils } from '@services/utils/text'; -import { CoreUtils } from '@services/utils/utils'; import { CoreDirectivesRegistry } from '@singletons/directives-registry'; import { CoreCoordinates, CoreDom } from '@singletons/dom'; import { CoreEventObserver } from '@singletons/events'; import { CoreLogger } from '@singletons/logger'; import { AddonModQuizDdwtosQuestionData } from '../component/ddwtos'; +import { CoreWait } from '@singletons/wait'; /** * Class to make a question of ddwtos type work. @@ -491,7 +491,7 @@ export class AddonQtypeDdwtosQuestion { } else { // Group items should always have a parent, add a fallback just in case. await CoreDom.waitToBeInDOM(groupItems[0]); - await CoreUtils.nextTicks(5); + await CoreWait.nextTicks(5); } // Find max height and width. diff --git a/src/addons/qtype/ordering/component/ordering.ts b/src/addons/qtype/ordering/component/ordering.ts index 5bd693420..bd4da3eda 100644 --- a/src/addons/qtype/ordering/component/ordering.ts +++ b/src/addons/qtype/ordering/component/ordering.ts @@ -18,7 +18,7 @@ import { CoreQuestionHelper } from '@features/question/services/question-helper' import { CoreDomUtils } from '@services/utils/dom'; import { ItemReorderEventDetail } from '@ionic/angular'; import { Translate } from '@singletons'; -import { CoreUtils } from '@services/utils/utils'; +import { CoreWait } from '@singletons/wait'; import { CorePlatform } from '@services/platform'; /** @@ -153,7 +153,7 @@ export class AddonQtypeOrderingComponent extends CoreQuestionBaseComponent {}, // eslint-disable-line @typescript-eslint/no-empty-function }); - await CoreUtils.nextTick(); + await CoreWait.nextTick(); // When moving an item to the first or last position, the button that was clicked will be hidden. In this case, we need to // focus the other button. Otherwise, re-focus the same button since the focus is lost in some cases. diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 4b93666b8..e821baaec 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -27,6 +27,7 @@ import { CorePlatform } from '@services/platform'; import { CoreLogger } from '@singletons/logger'; import { CorePromisedValue } from '@classes/promised-value'; import { register } from 'swiper/element/bundle'; +import { CoreWait } from '@singletons/wait'; register(); @@ -83,7 +84,7 @@ export class AppComponent implements OnInit, AfterViewInit { // Check if the path changes due to the back navigation handler, to know if we're at root level. // Ionic doc recommends IonRouterOutlet.canGoBack, but there's no easy way to get the current outlet from here. // The path seems to change immediately (0 ms timeout), but use 50ms just in case. - await CoreUtils.wait(50); + await CoreWait.wait(50); if (CoreNavigator.getCurrentPath() != initialPath) { // Ionic has navigated back, nothing else to do. diff --git a/src/core/classes/ion-loading.ts b/src/core/classes/ion-loading.ts index 1e831e54c..ff6b56cef 100644 --- a/src/core/classes/ion-loading.ts +++ b/src/core/classes/ion-loading.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { CoreUtils } from '@services/utils/utils'; +import { CoreWait } from '@singletons/wait'; import { LoadingController } from '@singletons'; /** @@ -105,7 +105,7 @@ export class CoreIonLoadingElement { // Wait a bit before presenting the modal, to prevent it being displayed if dismiss is called fast. this.scheduled = true; - await CoreUtils.wait(40); + await CoreWait.wait(40); if (!this.scheduled) { return; diff --git a/src/core/classes/page-load-watcher.ts b/src/core/classes/page-load-watcher.ts index 870dcc2ac..2bc2ef7aa 100644 --- a/src/core/classes/page-load-watcher.ts +++ b/src/core/classes/page-load-watcher.ts @@ -19,6 +19,7 @@ import { AsyncDirective } from './async-directive'; import { PageLoadsManager } from './page-loads-manager'; import { CorePromisedValue } from './promised-value'; import { WSObservable } from './sites/authenticated-site'; +import { CoreWait } from '@singletons/wait'; /** * Class to watch requests from a page load (including requests from page sub-components). @@ -111,7 +112,7 @@ export class PageLoadWatcher { this.checkHasLoaded(); // Subscription variable might not be set because the observable completed immediately. Wait for next tick. - await CoreUtils.nextTick(); + await CoreWait.nextTick(); subscription?.unsubscribe(); }; diff --git a/src/core/classes/sites/authenticated-site.ts b/src/core/classes/sites/authenticated-site.ts index be2204b73..25e98e451 100644 --- a/src/core/classes/sites/authenticated-site.ts +++ b/src/core/classes/sites/authenticated-site.ts @@ -42,6 +42,7 @@ import { Md5 } from 'ts-md5'; import { CoreUrlUtils } from '@services/utils/url'; import { CoreSiteWSCacheRecord } from '@services/database/sites'; import { CoreErrorLogs } from '@singletons/error-logs'; +import { CoreWait } from '@singletons/wait'; /** * Class that represents a site (combination of site + user) where the user has authenticated but the site hasn't been validated @@ -1584,7 +1585,7 @@ export function chainRequests>( firstValue = false; // Wait to see if the observable is completed (no more values). - await CoreUtils.nextTick(); + await CoreWait.nextTick(); if (isCompleted) { // Current request only returns cached data. Let chained requests update in background. @@ -1601,7 +1602,7 @@ export function chainRequests>( complete: async () => { isCompleted = true; - await CoreUtils.nextTick(); + await CoreWait.nextTick(); subscriber.complete(); }, diff --git a/src/core/classes/tabs.ts b/src/core/classes/tabs.ts index aae11a6b0..08d76b564 100644 --- a/src/core/classes/tabs.ts +++ b/src/core/classes/tabs.ts @@ -31,7 +31,7 @@ import { CoreSettingsHelper } from '@features/settings/services/settings-helper' import { CoreAriaRoleTab, CoreAriaRoleTabFindable } from './aria-role-tab'; import { CoreEventObserver } from '@singletons/events'; import { CoreDom } from '@singletons/dom'; -import { CoreUtils } from '@services/utils/utils'; +import { CoreWait } from '@singletons/wait'; import { CoreError } from './errors/error'; import { CorePromisedValue } from './promised-value'; import { AsyncDirective } from './async-directive'; @@ -218,7 +218,7 @@ export class CoreTabsBaseComponent implements AfterViewIn this.slideChanged(); this.swiper.update(); - await CoreUtils.nextTick(); + await CoreWait.nextTick(); if (!this.hasSliddenToInitial && this.selectedIndex && this.selectedIndex >= this.swiper.slidesPerViewDynamic()) { this.hasSliddenToInitial = true; @@ -344,7 +344,7 @@ export class CoreTabsBaseComponent implements AfterViewIn } this.maxSlides = 3; - await CoreUtils.nextTick(); + await CoreWait.nextTick(); if (!this.swiper.width) { return; diff --git a/src/core/classes/tests/queue-runner.test.ts b/src/core/classes/tests/queue-runner.test.ts index 748c7c848..4ead65ac1 100644 --- a/src/core/classes/tests/queue-runner.test.ts +++ b/src/core/classes/tests/queue-runner.test.ts @@ -13,7 +13,7 @@ // limitations under the License. import { CoreQueueRunner } from '@classes/queue-runner'; -import { CoreUtils } from '@services/utils/utils'; +import { CoreWait } from '@singletons/wait'; describe('CoreQueueRunner', () => { @@ -26,7 +26,7 @@ describe('CoreQueueRunner', () => { // Act await Promise.all(range.map((i) => lock.run(async () => { - await CoreUtils.wait(Math.floor(Math.random() * 10)); + await CoreWait.wait(Math.floor(Math.random() * 10)); items.push(`Item #${i}`); }))); diff --git a/src/core/components/infinite-loading/infinite-loading.ts b/src/core/components/infinite-loading/infinite-loading.ts index 0b519da2d..49103a3df 100644 --- a/src/core/components/infinite-loading/infinite-loading.ts +++ b/src/core/components/infinite-loading/infinite-loading.ts @@ -14,7 +14,7 @@ import { Component, Input, Output, EventEmitter, OnChanges, SimpleChange, ViewChild, ElementRef } from '@angular/core'; import { IonInfiniteScroll } from '@ionic/angular'; -import { CoreUtils } from '@services/utils/utils'; +import { CoreWait } from '@singletons/wait'; const THRESHOLD = .15; // % of the scroll element height that must be close to the edge to consider loading more items necessary. @@ -77,8 +77,8 @@ export class CoreInfiniteLoadingComponent implements OnChanges { } // Wait to allow items to render and scroll content to grow. - await CoreUtils.nextTick(); - await CoreUtils.waitFor(() => scrollElement.scrollHeight > scrollElement.clientHeight, { timeout: 1000 }); + await CoreWait.nextTick(); + await CoreWait.waitFor(() => scrollElement.scrollHeight > scrollElement.clientHeight, { timeout: 1000 }); // Calculate distance from edge. const infiniteHeight = this.hostElement.getBoundingClientRect().height; @@ -116,7 +116,7 @@ export class CoreInfiniteLoadingComponent implements OnChanges { */ async complete(): Promise { // Wait a bit before allowing loading more, otherwise it could be re-triggered automatically when it shouldn't. - await CoreUtils.wait(400); + await CoreWait.wait(400); await this.completeLoadMore(); } diff --git a/src/core/components/loading/loading.ts b/src/core/components/loading/loading.ts index 8e66276e4..f890c40ee 100644 --- a/src/core/components/loading/loading.ts +++ b/src/core/components/loading/loading.ts @@ -21,6 +21,7 @@ import { CoreDirectivesRegistry } from '@singletons/directives-registry'; import { CorePromisedValue } from '@classes/promised-value'; import { AsyncDirective } from '@classes/async-directive'; import { CorePlatform } from '@services/platform'; +import { CoreWait } from '@singletons/wait'; /** * Component to show a loading spinner and message while data is being loaded. @@ -72,13 +73,13 @@ export class CoreLoadingComponent implements OnInit, OnChanges, AfterViewInit, A // Throttle 20ms to let mutations resolve. const throttleMutation = CoreUtils.throttle(async () => { - await CoreUtils.nextTick(); + await CoreWait.nextTick(); if (!this.loaded) { return; } this.element.style.display = 'inline'; - await CoreUtils.nextTick(); + await CoreWait.nextTick(); this.element.style.removeProperty('display'); }, 20); diff --git a/src/core/components/sheet-modal/sheet-modal.ts b/src/core/components/sheet-modal/sheet-modal.ts index b3c409775..e93409c7c 100644 --- a/src/core/components/sheet-modal/sheet-modal.ts +++ b/src/core/components/sheet-modal/sheet-modal.ts @@ -17,7 +17,7 @@ import { AfterViewInit, Component, ElementRef, Input, ViewChild } from '@angular import { CoreModalComponent } from '@classes/modal-component'; import { CorePromisedValue } from '@classes/promised-value'; import { CoreModals } from '@services/modals'; -import { CoreUtils } from '@services/utils/utils'; +import { CoreWait } from '@singletons/wait'; import { AngularFrameworkDelegate } from '@singletons'; import { CoreDirectivesRegistry } from '@singletons/directives-registry'; @@ -64,13 +64,13 @@ export class CoreSheetModalComponent implements Af const wrapper = await this.wrapperElement; this.content = await AngularFrameworkDelegate.attachViewToDom(wrapper, this.component, this.componentProps ?? {}); - await CoreUtils.nextTick(); + await CoreWait.nextTick(); this.element.classList.add('active'); this.element.style.zIndex = `${20000 + CoreModals.getTopOverlayIndex()}`; - await CoreUtils.nextTick(); - await CoreUtils.wait(300); + await CoreWait.nextTick(); + await CoreWait.wait(300); const instance = CoreDirectivesRegistry.resolve(this.content, this.component); @@ -89,8 +89,8 @@ export class CoreSheetModalComponent implements Af this.element.classList.remove('active'); - await CoreUtils.nextTick(); - await CoreUtils.wait(300); + await CoreWait.nextTick(); + await CoreWait.wait(300); await AngularFrameworkDelegate.removeViewFromDom(wrapper, this.content); } diff --git a/src/core/components/swipe-slides/swipe-slides.ts b/src/core/components/swipe-slides/swipe-slides.ts index 0154671e1..182fdc890 100644 --- a/src/core/components/swipe-slides/swipe-slides.ts +++ b/src/core/components/swipe-slides/swipe-slides.ts @@ -20,7 +20,7 @@ import { CoreSwipeSlidesItemsManager } from '@classes/items-management/swipe-sli import { CorePromisedValue } from '@classes/promised-value'; import { IonContent } from '@ionic/angular'; import { CoreDomUtils, VerticalPoint } from '@services/utils/dom'; -import { CoreUtils } from '@services/utils/utils'; +import { CoreWait } from '@singletons/wait'; import { NgZone } from '@singletons'; import { CoreDom } from '@singletons/dom'; import { CoreEventObserver } from '@singletons/events'; @@ -230,7 +230,7 @@ export class CoreSwipeSlidesComponent implements OnChanges, OnDe */ protected async onItemsUpdated(): Promise { // Wait for slides to be added in DOM. - await CoreUtils.nextTick(); + await CoreWait.nextTick(); // Update the slides component so the slides list reflects the new items. await this.updateSlidesComponent(); @@ -348,7 +348,7 @@ export class CoreSwipeSlidesComponent implements OnChanges, OnDe this.swiper.update(); // We need to ensure the slides are updated before continuing. - await CoreUtils.nextTicks(2); + await CoreWait.nextTicks(2); } /** diff --git a/src/core/directives/auto-focus.ts b/src/core/directives/auto-focus.ts index eb550ac35..f531d74c0 100644 --- a/src/core/directives/auto-focus.ts +++ b/src/core/directives/auto-focus.ts @@ -17,6 +17,7 @@ import { Directive, Input, ElementRef, AfterViewInit } from '@angular/core'; import { CoreDomUtils } from '@services/utils/dom'; import { CoreUtils } from '@services/utils/utils'; import { CoreDom } from '@singletons/dom'; +import { CoreWait } from '@singletons/wait'; /** * Directive to auto focus an element when a view is loaded. @@ -51,7 +52,7 @@ export class CoreAutoFocusDirective implements AfterViewInit { // Wait in case there is an animation to enter the page, otherwise the interaction // between the keyboard appearing and the animation causes a visual glitch. - await CoreUtils.wait(540); + await CoreWait.wait(540); CoreDomUtils.focusElement(this.element); diff --git a/src/core/directives/collapsible-footer.ts b/src/core/directives/collapsible-footer.ts index de58fcedb..6fd746d42 100644 --- a/src/core/directives/collapsible-footer.ts +++ b/src/core/directives/collapsible-footer.ts @@ -23,6 +23,7 @@ import { CoreEventObserver } from '@singletons/events'; import { CoreLoadingComponent } from '@components/loading/loading'; import { CoreCancellablePromise } from '@classes/cancellable-promise'; import { CoreDom } from '@singletons/dom'; +import { CoreWait } from '@singletons/wait'; /** * Directive to make an element fixed at the bottom collapsible when scrolling. @@ -101,7 +102,7 @@ export class CoreCollapsibleFooterDirective implements OnInit, OnDestroy { await this.viewportPromise; this.element.classList.remove('is-active'); - await CoreUtils.nextTick(); + await CoreWait.nextTick(); // Set a minimum height value. this.initialHeight = this.element.getBoundingClientRect().height || this.initialHeight; diff --git a/src/core/directives/collapsible-header.ts b/src/core/directives/collapsible-header.ts index 30613d0c3..b20031439 100644 --- a/src/core/directives/collapsible-header.ts +++ b/src/core/directives/collapsible-header.ts @@ -27,6 +27,7 @@ import { CoreEventObserver, CoreEvents } from '@singletons/events'; import { CoreMath } from '@singletons/math'; import { Subscription } from 'rxjs'; import { CoreFormatTextDirective } from './format-text'; +import { CoreWait } from '@singletons/wait'; declare module '@singletons/events' { @@ -344,7 +345,7 @@ export class CoreCollapsibleHeaderDirective implements OnInit, OnChanges, OnDest await this.visiblePromise; this.page.classList.remove('collapsible-header-page-is-active'); - await CoreUtils.nextTick(); + await CoreWait.nextTick(); // Add floating title and measure initial position. const collapsedHeaderTitle = this.collapsedHeader.querySelector('h1') as HTMLHeadingElement; @@ -429,7 +430,7 @@ export class CoreCollapsibleHeaderDirective implements OnInit, OnChanges, OnDest } // Make sure elements have been added to the DOM. - await CoreUtils.nextTick(); + await CoreWait.nextTick(); // Wait all loadings and tabs to finish loading. await CoreDirectivesRegistry.waitMultipleDirectivesReady(this.page, [ diff --git a/src/core/directives/format-text.ts b/src/core/directives/format-text.ts index 3bac96c9d..4d8f34b0d 100644 --- a/src/core/directives/format-text.ts +++ b/src/core/directives/format-text.ts @@ -56,6 +56,7 @@ import { FrameElement, FrameElementController } from '@classes/element-controlle import { CoreUrl } from '@singletons/url'; import { CoreIcons } from '@singletons/icons'; import { ContextLevel } from '../constants'; +import { CoreWait } from '@singletons/wait'; /** * Directive to format text rendered. It renders the HTML and treats all links and media, using CoreLinkDirective @@ -339,7 +340,7 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncDirec // Show the element again. this.element.classList.remove('core-loading'); - await CoreUtils.nextTick(); + await CoreWait.nextTick(); // Emit the afterRender output. this.afterRender.emit(); @@ -380,7 +381,7 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncDirec this.elementControllers.forEach(controller => controller.destroy()); this.elementControllers = result.elementControllers; - await CoreUtils.nextTick(); + await CoreWait.nextTick(); // Add magnifying glasses to images. this.addImageViewerButton(); @@ -705,7 +706,7 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncDirec const previousDisplay = getComputedStyle(this.element).display; this.element.style.display = 'inline-block'; - await CoreUtils.nextTick(); + await CoreWait.nextTick(); width = this.element.getBoundingClientRect().width; diff --git a/src/core/features/block/components/side-blocks/side-blocks.ts b/src/core/features/block/components/side-blocks/side-blocks.ts index e80b9a3b6..b677274a5 100644 --- a/src/core/features/block/components/side-blocks/side-blocks.ts +++ b/src/core/features/block/components/side-blocks/side-blocks.ts @@ -23,6 +23,7 @@ import { CoreCoursesDashboard } from '@features/courses/services/dashboard'; import { CoreTextUtils } from '@services/utils/text'; import { CoreDom } from '@singletons/dom'; import { ContextLevel } from '@/core/constants'; +import { CoreWait } from '@singletons/wait'; /** * Component that displays the list of side blocks. @@ -135,8 +136,8 @@ export class CoreBlockSideBlocksComponent implements OnInit { const selector = '#block-' + this.initialBlockInstanceId; - await CoreUtils.waitFor(() => !!this.elementRef.nativeElement.querySelector(selector)); - await CoreUtils.wait(200); + await CoreWait.waitFor(() => !!this.elementRef.nativeElement.querySelector(selector)); + await CoreWait.wait(200); CoreDom.scrollToElement(this.elementRef.nativeElement, selector, { addYAxis: -10 }); } diff --git a/src/core/features/compile/services/compile.ts b/src/core/features/compile/services/compile.ts index f85f156b8..daee0221a 100644 --- a/src/core/features/compile/services/compile.ts +++ b/src/core/features/compile/services/compile.ts @@ -85,6 +85,7 @@ import { CorePath } from '@singletons/path'; import { CoreText } from '@singletons/text'; import { CoreTime } from '@singletons/time'; import { CoreUrl } from '@singletons/url'; +import { CoreWait } from '@singletons/wait'; import { CoreWindow } from '@singletons/window'; import { CoreCache } from '@classes/cache'; import { CoreDelegate } from '@classes/delegate'; @@ -314,6 +315,7 @@ export class CoreCompileProvider { instance['CoreText'] = CoreText; instance['CoreTime'] = CoreTime; instance['CoreUrl'] = CoreUrl; + instance['CoreWait'] = CoreWait; instance['CoreWindow'] = CoreWindow; instance['CoreCache'] = CoreCache; // @deprecated since 4.4, plugins should use plain objects instead. instance['CoreDelegate'] = CoreDelegate; diff --git a/src/core/features/course/components/course-index/course-index.ts b/src/core/features/course/components/course-index/course-index.ts index 804846ee3..6fb8c8ca3 100644 --- a/src/core/features/course/components/course-index/course-index.ts +++ b/src/core/features/course/components/course-index/course-index.ts @@ -24,7 +24,7 @@ import { CoreCourseFormatDelegate } from '@features/course/services/format-deleg import { CoreCourseAnyCourseData } from '@features/courses/services/courses'; import { CoreCoursesHelper } from '@features/courses/services/courses-helper'; import { CoreSites } from '@services/sites'; -import { CoreUtils } from '@services/utils/utils'; +import { CoreWait } from '@singletons/wait'; import { ModalController } from '@singletons'; import { CoreDom } from '@singletons/dom'; @@ -123,11 +123,11 @@ export class CoreCourseCourseIndexComponent implements OnInit { this.highlighted = CoreCourseFormatDelegate.getSectionHightlightedName(this.course); // Wait a bit to render the data, otherwise the modal takes a while to appear in big courses or slow devices. - await CoreUtils.wait(400); + await CoreWait.wait(400); this.loaded = true; - await CoreUtils.nextTick(); + await CoreWait.nextTick(); CoreDom.scrollToElement( this.elementRef.nativeElement, diff --git a/src/core/features/course/pages/contents/contents.ts b/src/core/features/course/pages/contents/contents.ts index af1ef4e35..b4e5c50d7 100644 --- a/src/core/features/course/pages/contents/contents.ts +++ b/src/core/features/course/pages/contents/contents.ts @@ -42,6 +42,7 @@ import { CoreNavigator } from '@services/navigator'; import { CoreRefreshContext, CORE_REFRESH_CONTEXT } from '@/core/utils/refresh-context'; import { CoreCoursesHelper } from '@features/courses/services/courses-helper'; import { CoreSites } from '@services/sites'; +import { CoreWait } from '@singletons/wait'; /** * Page that displays the contents of a course. @@ -411,7 +412,7 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy, CoreRefreshCon this.changeDetectorRef.detectChanges(); if (scrollTop > 0) { - await CoreUtils.nextTick(); + await CoreWait.nextTick(); this.content?.scrollToPoint(0, scrollTop, 0); } } diff --git a/src/core/features/course/pages/index/index.ts b/src/core/features/course/pages/index/index.ts index dbb693220..16955652f 100644 --- a/src/core/features/course/pages/index/index.ts +++ b/src/core/features/course/pages/index/index.ts @@ -30,6 +30,7 @@ import { CoreCoursesHelper, CoreCourseWithImageAndColor } from '@features/course import { CoreColors } from '@singletons/colors'; import { CorePath } from '@singletons/path'; import { CoreSites } from '@services/sites'; +import { CoreWait } from '@singletons/wait'; /** * Page that displays the list of courses the user is enrolled in. @@ -209,7 +210,7 @@ export class CoreCourseIndexPage implements OnInit, OnDestroy { // Select the tab if needed. this.firstTabName = undefined; if (tabToLoad) { - await CoreUtils.nextTick(); + await CoreWait.nextTick(); this.tabsComponent?.selectByIndex(tabToLoad); } diff --git a/src/core/features/courses/pages/my/my.ts b/src/core/features/courses/pages/my/my.ts index ddac3c1ed..f4f1ff63b 100644 --- a/src/core/features/courses/pages/my/my.ts +++ b/src/core/features/courses/pages/my/my.ts @@ -31,6 +31,7 @@ import { CoreCourses } from '../../services/courses'; import { CoreTime } from '@singletons/time'; import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; import { Translate } from '@singletons'; +import { CoreWait } from '@singletons/wait'; /** * Page that shows a my courses. @@ -129,7 +130,7 @@ export class CoreCoursesMyPage implements OnInit, OnDestroy, AsyncDirective { this.loadedBlock = blocks.mainBlocks.concat(blocks.sideBlocks).find((block) => block.name == 'myoverview'); this.hasSideBlocks = supportsMyParam && CoreBlockDelegate.hasSupportedBlock(blocks.sideBlocks); - await CoreUtils.nextTicks(2); + await CoreWait.nextTicks(2); this.myOverviewBlock = this.block?.dynamicComponent?.instance as AddonBlockMyOverviewComponent; diff --git a/src/core/features/editor/components/rich-text-editor/rich-text-editor.ts b/src/core/features/editor/components/rich-text-editor/rich-text-editor.ts index 44182ac56..ced2b6dcf 100644 --- a/src/core/features/editor/components/rich-text-editor/rich-text-editor.ts +++ b/src/core/features/editor/components/rich-text-editor/rich-text-editor.ts @@ -46,6 +46,7 @@ import { SwiperOptions } from 'swiper/types'; import { ContextLevel } from '@/core/constants'; import { CoreSwiper } from '@singletons/swiper'; import { CoreTextUtils } from '@services/utils/text'; +import { CoreWait } from '@singletons/wait'; /** * Component to display a rich text editor if enabled. @@ -369,7 +370,7 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterViewInit, * @returns Blank height in px. Will be negative if no blank space. */ protected async getBlankHeightInContent(): Promise { - await CoreUtils.nextTicks(5); // Ensure content is completely loaded in the DOM. + await CoreWait.nextTicks(5); // Ensure content is completely loaded in the DOM. let content: Element | null = this.element.closest('ion-content'); const contentHeight = await CoreDomUtils.getContentHeight(this.content); @@ -485,7 +486,7 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterViewInit, this.textareaElement?.removeAttribute('hidden'); } - await CoreUtils.nextTick(); + await CoreWait.nextTick(); this.focusRTE(event); } @@ -826,7 +827,7 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterViewInit, this.toolbarArrows = true; } - await CoreUtils.nextTick(); + await CoreWait.nextTick(); this.toolbarSlides.update(); diff --git a/src/core/features/emulator/services/local-notifications.ts b/src/core/features/emulator/services/local-notifications.ts index 7e68a2f65..7c9b4c61b 100644 --- a/src/core/features/emulator/services/local-notifications.ts +++ b/src/core/features/emulator/services/local-notifications.ts @@ -15,7 +15,7 @@ import { CoreError } from '@classes/errors/error'; import { ILocalNotification, ILocalNotificationAction, LocalNotifications } from '@awesome-cordova-plugins/local-notifications/ngx'; import { Observable, Subject } from 'rxjs'; -import { CoreUtils } from '@services/utils/utils'; +import { CoreWait } from '@singletons/wait'; import { CorePlatform } from '@services/platform'; /** @@ -340,7 +340,7 @@ export class LocalNotificationsMock extends LocalNotifications { // In some testing environments, Notification.requestPermission gets stuck and never returns. // Given that we don't actually need browser notifications to work in Behat tests, we can just // continue if the permissions haven't been granted after 1 second. - permissionRequests.push(CoreUtils.wait(1000).then(() => 'granted')); + permissionRequests.push(CoreWait.wait(1000).then(() => 'granted')); } const permission = await Promise.race(permissionRequests); diff --git a/src/core/features/login/components/site-help/site-help.ts b/src/core/features/login/components/site-help/site-help.ts index 6bee64115..75e9efb56 100644 --- a/src/core/features/login/components/site-help/site-help.ts +++ b/src/core/features/login/components/site-help/site-help.ts @@ -21,6 +21,7 @@ import { CoreDomUtils } from '@services/utils/dom'; import { CoreCancellablePromise } from '@classes/cancellable-promise'; import { SubPartial } from '@/core/utils/types'; import { CoreSharedModule } from '@/core/shared.module'; +import { CoreWait } from '@singletons/wait'; /** * Component that displays help to connect to a site. @@ -118,7 +119,7 @@ export class CoreLoginSiteHelpComponent implements AfterViewInit, OnDestroy { const answers = Array.from(this.el.nativeElement.querySelectorAll('.core-login-site-help--answer')); await Promise.all(answers.map(async answer => { - await this.track(CoreUtils.waitFor(() => answer.clientHeight !== 0)); + await this.track(CoreWait.waitFor(() => answer.clientHeight !== 0)); await this.track(CoreDomUtils.waitForImages(answer)); answer.style.setProperty('--height', `${answer.clientHeight}px`); diff --git a/src/core/features/mainmenu/pages/menu/menu.ts b/src/core/features/mainmenu/pages/menu/menu.ts index f75066f04..1c62bf7ad 100644 --- a/src/core/features/mainmenu/pages/menu/menu.ts +++ b/src/core/features/mainmenu/pages/menu/menu.ts @@ -31,6 +31,7 @@ import { CoreSites } from '@services/sites'; import { CoreDom } from '@singletons/dom'; import { CoreLogger } from '@singletons/logger'; import { CorePlatform } from '@services/platform'; +import { CoreWait } from '@singletons/wait'; const ANIMATION_DURATION = 500; @@ -203,7 +204,7 @@ export class CoreMainMenuPage implements OnInit, OnDestroy { if (this.loaded && (!mainMenuTab || removedHandlersPages.includes(mainMenuTab))) { // No tab selected or handler no longer available, select the first one. - await CoreUtils.nextTick(); + await CoreWait.nextTick(); const tabPage = this.tabs[0] ? this.tabs[0].page : this.morePageName; const tabPageParams = this.tabs[0] ? this.tabs[0].pageParams : {}; @@ -330,9 +331,9 @@ export class CoreMainMenuPage implements OnInit, OnDestroy { * Notify that the menu visibility has been updated. */ protected async notifyVisibilityUpdated(): Promise { - await CoreUtils.nextTick(); - await CoreUtils.wait(ANIMATION_DURATION); - await CoreUtils.nextTick(); + await CoreWait.nextTick(); + await CoreWait.wait(ANIMATION_DURATION); + await CoreWait.nextTick(); CoreEvents.trigger(CoreMainMenuProvider.MAIN_MENU_VISIBILITY_UPDATED); } diff --git a/src/core/features/policy/pages/site-policy/site-policy.ts b/src/core/features/policy/pages/site-policy/site-policy.ts index aca9f223d..bcb986566 100644 --- a/src/core/features/policy/pages/site-policy/site-policy.ts +++ b/src/core/features/policy/pages/site-policy/site-policy.ts @@ -30,6 +30,7 @@ import { IonContent } from '@ionic/angular'; import { CoreScreen } from '@services/screen'; import { Subscription } from 'rxjs'; import { CoreDom } from '@singletons/dom'; +import { CoreWait } from '@singletons/wait'; /** * Page to accept a site policy. @@ -337,7 +338,7 @@ export class CorePolicySitePolicyPage implements OnInit, OnDestroy { * Check if the content has scroll. */ protected async checkScroll(): Promise { - await CoreUtils.wait(400); + await CoreWait.wait(400); const scrollElement = await this.content?.getScrollElement(); diff --git a/src/core/features/reminders/components/set-reminder-menu/set-reminder-menu.ts b/src/core/features/reminders/components/set-reminder-menu/set-reminder-menu.ts index 26e55c901..bdc6cb875 100644 --- a/src/core/features/reminders/components/set-reminder-menu/set-reminder-menu.ts +++ b/src/core/features/reminders/components/set-reminder-menu/set-reminder-menu.ts @@ -20,7 +20,7 @@ import { CoreReminderValueAndUnit, } from '@features/reminders/services/reminders'; import { CoreDomUtils } from '@services/utils/dom'; -import { CoreUtils } from '@services/utils/utils'; +import { CoreWait } from '@singletons/wait'; import { PopoverController } from '@singletons'; import { CoreRemindersSetReminderCustomComponent } from '../set-reminder-custom/set-reminder-custom'; @@ -175,7 +175,7 @@ export class CoreRemindersSetReminderMenuComponent implements OnInit { this.customLabel = CoreReminders.getUnitValueLabel(this.customValue, this.customUnits); // Let the dimissed popover to be removed. - await CoreUtils.nextTick(); + await CoreWait.nextTick(); PopoverController.dismiss({ timeBefore: Math.abs(this.customValue) * this.customUnits }); } diff --git a/src/core/features/usertours/services/user-tours.ts b/src/core/features/usertours/services/user-tours.ts index f497c07bb..463f79287 100644 --- a/src/core/features/usertours/services/user-tours.ts +++ b/src/core/features/usertours/services/user-tours.ts @@ -27,6 +27,7 @@ import { CoreSubscriptions } from '@singletons/subscriptions'; import { CoreUserToursUserTourComponent } from '../components/user-tour/user-tour'; import { APP_SCHEMA, CoreUserToursDBEntry, USER_TOURS_TABLE_NAME } from './database/user-tours'; import { CorePromisedValue } from '@classes/promised-value'; +import { CoreWait } from '@singletons/wait'; /** * Service to manage User Tours. @@ -113,7 +114,7 @@ export class CoreUserToursService { protected async show(options: CoreUserToursBasicOptions | CoreUserToursFocusedOptions): Promise { const { delay, ...componentOptions } = options; - await CoreUtils.wait(delay ?? 200); + await CoreWait.wait(delay ?? 200); options.after && await this.waitForUserTour(options.after, options.afterTimeout); diff --git a/src/core/services/error-accordion.ts b/src/core/services/error-accordion.ts index 33c137780..b87a46aba 100644 --- a/src/core/services/error-accordion.ts +++ b/src/core/services/error-accordion.ts @@ -14,7 +14,7 @@ import { Injectable } from '@angular/core'; import { Translate, makeSingleton } from '@singletons'; -import { CoreUtils } from '@services/utils/utils'; +import { CoreWait } from '@singletons/wait'; import { CoreDom } from '@singletons/dom'; import { CoreForms } from '@singletons/form'; import { CoreLogger } from '@singletons/logger'; @@ -113,7 +113,7 @@ export class CoreErrorAccordionService { wrapper.style.setProperty('--description-height', `${description.clientHeight}px`); wrapper.classList.add('hydrated'); - await CoreUtils.nextTick(); + await CoreWait.nextTick(); hideText.style.display = 'revert'; } diff --git a/src/core/services/tests/sites.test.ts b/src/core/services/tests/sites.test.ts index 1e91991af..7a274b4f2 100644 --- a/src/core/services/tests/sites.test.ts +++ b/src/core/services/tests/sites.test.ts @@ -22,7 +22,7 @@ import { Http } from '@singletons'; import { of } from 'rxjs'; import { CoreSite } from '@classes/sites/site'; import { CoreHTMLClasses } from '@singletons/html-classes'; -import { CoreUtils } from '@services/utils/utils'; +import { CoreWait } from '@singletons/wait'; describe('CoreSitesProvider', () => { @@ -74,7 +74,7 @@ describe('CoreSitesProvider', () => { CoreEvents.trigger(CoreEvents.LOGIN, {}, '42'); // Wait the event to be processed. - await CoreUtils.nextTick(); + await CoreWait.nextTick(); expect(document.documentElement.classList.contains('theme-site-'+themeName)).toBe(true); expect(document.documentElement.classList.contains('theme-site-'+themeName2)).toBe(false); @@ -86,7 +86,7 @@ describe('CoreSitesProvider', () => { CoreEvents.trigger(CoreEvents.SITE_UPDATED, site.infos , '42'); // Wait the event to be processed. - await CoreUtils.nextTick(); + await CoreWait.nextTick(); expect(document.documentElement.classList.contains('theme-site-'+themeName2)).toBe(true); expect(document.documentElement.classList.contains('theme-site-'+themeName)).toBe(false); @@ -99,7 +99,7 @@ describe('CoreSitesProvider', () => { CoreEvents.trigger(CoreEvents.SITE_ADDED, site.infos , '42'); // Wait the event to be processed. - await CoreUtils.nextTick(); + await CoreWait.nextTick(); expect(document.documentElement.classList.contains('theme-site-'+themeName2)).toBe(true); expect(document.documentElement.classList.contains('theme-site-'+themeName)).toBe(false); diff --git a/src/core/services/utils/dom.ts b/src/core/services/utils/dom.ts index 38846b8e9..5f238e435 100644 --- a/src/core/services/utils/dom.ts +++ b/src/core/services/utils/dom.ts @@ -59,6 +59,7 @@ import { CorePasswordModalParams, CorePasswordModalResponse } from '@components/ import { CoreWSError } from '@classes/errors/wserror'; import { CoreErrorLogs } from '@singletons/error-logs'; import { CoreKeyboard } from '@singletons/keyboard'; +import { CoreWait } from '@singletons/wait'; /* * "Utils" service with helper functions for UI, DOM elements and HTML code. @@ -333,7 +334,7 @@ export class CoreDomUtilsProvider { elementToFocus.focus(); if (elementToFocus === document.activeElement || (isIonButton && element === document.activeElement)) { - await CoreUtils.nextTick(); + await CoreWait.nextTick(); if (CorePlatform.isAndroid() && this.supportsInputKeyboard(elementToFocus)) { // On some Android versions the keyboard doesn't open automatically. CoreKeyboard.open(); @@ -342,7 +343,7 @@ export class CoreDomUtilsProvider { } // @TODO Probably a Mutation Observer would get this working. - await CoreUtils.wait(50); + await CoreWait.wait(50); retries--; } } @@ -1762,7 +1763,7 @@ export class CoreDomUtilsProvider { } // Wait a bit and try again. - await CoreUtils.wait(50); + await CoreWait.wait(50); return this.waitForResizeDone(windowWidth, windowHeight, retries+1); } diff --git a/src/core/services/utils/utils.ts b/src/core/services/utils/utils.ts index cc05c35ae..ea2b2e1f4 100644 --- a/src/core/services/utils/utils.ts +++ b/src/core/services/utils/utils.ts @@ -41,6 +41,7 @@ import { CoreUrlUtils } from './url'; import { QRScanner } from '@features/native/plugins'; import { CoreArray } from '@singletons/array'; import { CoreText } from '@singletons/text'; +import { CoreWait, CoreWaitOptions } from '@singletons/wait'; export type TreeNode = T & { children: TreeNode[] }; @@ -1783,10 +1784,10 @@ export class CoreUtilsProvider { * Wait some time. * * @param milliseconds Number of milliseconds to wait. - * @returns Promise resolved after the time has passed. + * @deprecated since 4.5. Use CoreWait.wait instead. */ - wait(milliseconds: number): Promise { - return new Promise(resolve => setTimeout(resolve, milliseconds)); + async wait(milliseconds: number): Promise { + await CoreWait.wait(milliseconds); } /** @@ -1794,51 +1795,34 @@ export class CoreUtilsProvider { * * @param condition Condition. * @returns Cancellable promise. + * @deprecated since 4.5. Use CoreWait.waitFor instead. */ waitFor(condition: () => boolean): CoreCancellablePromise; - waitFor(condition: () => boolean, options: CoreUtilsWaitOptions): CoreCancellablePromise; + waitFor(condition: () => boolean, options: CoreWaitOptions): CoreCancellablePromise; waitFor(condition: () => boolean, interval: number): CoreCancellablePromise; - waitFor(condition: () => boolean, optionsOrInterval: CoreUtilsWaitOptions | number = {}): CoreCancellablePromise { + waitFor(condition: () => boolean, optionsOrInterval: CoreWaitOptions | number = {}): CoreCancellablePromise { const options = typeof optionsOrInterval === 'number' ? { interval: optionsOrInterval } : optionsOrInterval; - if (condition()) { - return CoreCancellablePromise.resolve(); - } - - const startTime = Date.now(); - let intervalId: number | undefined; - - return new CoreCancellablePromise( - async (resolve) => { - intervalId = window.setInterval(() => { - if (!condition() && (!options.timeout || (Date.now() - startTime < options.timeout))) { - return; - } - - resolve(); - window.clearInterval(intervalId); - }, options.interval ?? 50); - }, - () => window.clearInterval(intervalId), - ); + return CoreWait.waitFor(condition, options); } /** * Wait until the next tick. * - * @returns Promise resolved when tick has been done. + * @deprecated since 4.5. Use CoreWait.nextTick instead. */ - nextTick(): Promise { - return this.wait(0); + async nextTick(): Promise { + await CoreWait.nextTick(); } /** * Wait until several next ticks. + * + * @param numTicks Number of ticks to wait. + * @deprecated since 4.5. Use CoreWait.nextTicks instead. */ async nextTicks(numTicks = 0): Promise { - for (let i = 0; i < numTicks; i++) { - await this.wait(0); - } + await CoreWait.nextTicks(numTicks); } /** @@ -1916,11 +1900,10 @@ export type CoreUtilsOpenInAppOptions = InAppBrowserOptions & { /** * Options for waiting. + * + * @deprecated since 4.5. Use CoreWaitOptions instead. */ -export type CoreUtilsWaitOptions = { - interval?: number; - timeout?: number; -}; +export type CoreUtilsWaitOptions = CoreWaitOptions; /** * Possible default picker actions. diff --git a/src/core/singletons/directives-registry.ts b/src/core/singletons/directives-registry.ts index 63642e12d..50fc996b6 100644 --- a/src/core/singletons/directives-registry.ts +++ b/src/core/singletons/directives-registry.ts @@ -14,7 +14,7 @@ import { Directive } from '@angular/core'; import { AsyncDirective } from '@classes/async-directive'; -import { CoreUtils } from '@services/utils/utils'; +import { CoreWait } from './wait'; import { CoreLogger } from './logger'; /** @@ -135,7 +135,7 @@ export class CoreDirectivesRegistry { })); // Wait for next tick to ensure directives are completely rendered. - await CoreUtils.nextTick(); + await CoreWait.nextTick(); // Check if there are new elements now that the found elements are ready (there could be nested elements). if (elements.length !== findElements().length) { @@ -181,7 +181,7 @@ export class CoreDirectivesRegistry { })); // Wait for next tick to ensure directives are completely rendered. - await CoreUtils.nextTick(); + await CoreWait.nextTick(); // Check if there are new elements now that the found elements are ready (there could be nested elements). const elementsAfterReady = directives.reduce((elements, directive) => { diff --git a/src/core/singletons/subscriptions.ts b/src/core/singletons/subscriptions.ts index efae69236..b0cea166d 100644 --- a/src/core/singletons/subscriptions.ts +++ b/src/core/singletons/subscriptions.ts @@ -13,7 +13,7 @@ // limitations under the License. import { EventEmitter } from '@angular/core'; -import { CoreUtils } from '@services/utils/utils'; +import { CoreWait } from './wait'; import { Observable, Subscription } from 'rxjs'; /** @@ -52,7 +52,7 @@ export class CoreSubscriptions { }; const unsubscribe = async () => { // Subscription variable might not be set because we can receive a value immediately. Wait for next tick. - await CoreUtils.nextTick(); + await CoreWait.nextTick(); subscription?.unsubscribe(); }; diff --git a/src/core/singletons/wait.ts b/src/core/singletons/wait.ts new file mode 100644 index 000000000..d2a633464 --- /dev/null +++ b/src/core/singletons/wait.ts @@ -0,0 +1,94 @@ +// (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 { CoreCancellablePromise } from '@classes/cancellable-promise'; + +/** + * Singleton with helper functions to wait. + */ +export class CoreWait { + + /** + * Wait until the next tick. + * + * @returns Promise resolved when tick has been done. + */ + static async nextTick(): Promise { + return CoreWait.wait(0); + } + + /** + * Wait until several next ticks. + * + * @param numTicks Number of ticks to wait. + */ + static async nextTicks(numTicks = 0): Promise { + for (let i = 0; i < numTicks; i++) { + await CoreWait.wait(0); + } + } + + /** + * Wait some time. + * + * @param milliseconds Number of milliseconds to wait. + * @returns Promise resolved after the time has passed. + */ + static wait(milliseconds: number): Promise { + return new Promise(resolve => setTimeout(resolve, milliseconds)); + } + + /** + * Wait until a given condition is met. + * + * @param condition Condition. + * @returns Cancellable promise. + */ + static waitFor(condition: () => boolean): CoreCancellablePromise; + static waitFor(condition: () => boolean, options: CoreWaitOptions): CoreCancellablePromise; + static waitFor(condition: () => boolean, interval: number): CoreCancellablePromise; + static waitFor(condition: () => boolean, optionsOrInterval: CoreWaitOptions | number = {}): CoreCancellablePromise { + const options = typeof optionsOrInterval === 'number' ? { interval: optionsOrInterval } : optionsOrInterval; + + if (condition()) { + return CoreCancellablePromise.resolve(); + } + + const startTime = Date.now(); + let intervalId: number | undefined; + + return new CoreCancellablePromise( + async (resolve) => { + intervalId = window.setInterval(() => { + if (!condition() && (!options.timeout || (Date.now() - startTime < options.timeout))) { + return; + } + + resolve(); + window.clearInterval(intervalId); + }, options.interval ?? 50); + }, + () => window.clearInterval(intervalId), + ); + } + +} + +/** + * Options for waiting. + */ +export type CoreWaitOptions = { + interval?: number; + timeout?: number; +}; diff --git a/src/testing/services/behat-blocking.ts b/src/testing/services/behat-blocking.ts index 716ed4438..b58407b0d 100644 --- a/src/testing/services/behat-blocking.ts +++ b/src/testing/services/behat-blocking.ts @@ -13,7 +13,7 @@ // limitations under the License. import { Injectable } from '@angular/core'; -import { CoreUtils } from '@services/utils/utils'; +import { CoreWait } from '@singletons/wait'; import { makeSingleton, NgZone } from '@singletons'; import { BehatTestsWindow, TestingBehatRuntime } from './behat-runtime'; @@ -117,7 +117,7 @@ export class TestingBehatBlockingService { // https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#timers // "This API does not guarantee that timers will run exactly on schedule. // Delays due to CPU load, other tasks, etc, are to be expected." - await CoreUtils.nextTicks(10); + await CoreWait.nextTicks(10); } // Check there isn't a spinner... @@ -193,7 +193,7 @@ export class TestingBehatBlockingService { * (and if not, removes it). */ protected async checkUIBlocked(): Promise { - await CoreUtils.nextTick(); + await CoreWait.nextTick(); const blockingElements = Array.from( document.querySelectorAll('div.core-loading-container, ion-loading'), diff --git a/src/testing/services/behat-dom.ts b/src/testing/services/behat-dom.ts index 7c6acb55e..d053e331e 100644 --- a/src/testing/services/behat-dom.ts +++ b/src/testing/services/behat-dom.ts @@ -14,7 +14,7 @@ import { Injectable } from '@angular/core'; import { CorePromisedValue } from '@classes/promised-value'; -import { CoreUtils } from '@services/utils/utils'; +import { CoreWait } from '@singletons/wait'; import { makeSingleton, NgZone } from '@singletons'; import { TestingBehatElementLocator, TestingBehatFindOptions } from './behat-runtime'; @@ -756,7 +756,7 @@ export class TestingBehatDomUtilsService { // Pretend we have cut and pasted the new text. if (element.tagName !== 'ION-SELECT' && getValue() !== '') { - await CoreUtils.nextTick(); + await CoreWait.nextTick(); await setValue(''); element.dispatchEvent(new InputEvent('input', { @@ -768,7 +768,7 @@ export class TestingBehatDomUtilsService { } if (value !== '') { - await CoreUtils.nextTick(); + await CoreWait.nextTick(); await setValue(value); element.dispatchEvent(new InputEvent('input', {