MOBILE-4616 chore: Create CoreWait to add waiting functions

main
Pau Ferrer Ocaña 2024-07-22 17:06:19 +02:00
parent 7068db3f62
commit 7d9c9b6fe9
46 changed files with 228 additions and 124 deletions

View File

@ -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));
}

View File

@ -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);

View File

@ -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<AddonMessage
// In some Moodle versions, wait 1 second to make sure timecreated is different.
// This is because there was a bug where messages with the same timecreated had a wrong order.
if (!groupMessagingEnabled && i < messages.length - 1) {
await CoreUtils.wait(1000);
await CoreWait.wait(1000);
}
}

View File

@ -16,7 +16,7 @@ import { AfterViewInit, Component, ViewChild } from '@angular/core';
import { CoreCourseModuleMainActivityPage } from '@features/course/classes/main-activity-page';
import { AddonModAssignIndexComponent } from '../../components/index/index';
import { CoreNavigator } from '@services/navigator';
import { CoreUtils } from '@services/utils/utils';
import { CoreWait } from '@singletons/wait';
/**
* Page that displays an assign.
@ -44,7 +44,7 @@ export class AddonModAssignIndexPage extends CoreCourseModuleMainActivityPage<Ad
async ngAfterViewInit(): Promise<void> {
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;

View File

@ -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<void> {
// Need a timeout to leave time to the view to be rendered.
await CoreUtils.nextTick();
await CoreWait.nextTick();
if (!this.viewDestroyed) {
this.content?.scrollToBottom();
}

View File

@ -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<void> {
await CoreUtils.nextTick();
await CoreWait.nextTick();
await CoreDirectivesRegistry.waitDirectivesReady(this.elementRef.nativeElement, 'core-question');
await CoreDom.scrollToElement(
this.elementRef.nativeElement,

View File

@ -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);
}

View File

@ -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);

View File

@ -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.

View File

@ -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<Addon
complete: () => {}, // 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.

View File

@ -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.

View File

@ -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;

View File

@ -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();
};

View File

@ -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<T, O extends ObservableInput<any>>(
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<T, O extends ObservableInput<any>>(
complete: async () => {
isCompleted = true;
await CoreUtils.nextTick();
await CoreWait.nextTick();
subscriber.complete();
},

View File

@ -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<T extends CoreTabBase> 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<T extends CoreTabBase> implements AfterViewIn
}
this.maxSlides = 3;
await CoreUtils.nextTick();
await CoreWait.nextTick();
if (!this.swiper.width) {
return;

View File

@ -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}`);
})));

View File

@ -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<void> {
// 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();
}

View File

@ -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);

View File

@ -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<T extends CoreModalComponent> 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<T extends CoreModalComponent> 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);
}

View File

@ -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<Item = unknown> implements OnChanges, OnDe
*/
protected async onItemsUpdated(): Promise<void> {
// 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<Item = unknown> implements OnChanges, OnDe
this.swiper.update();
// We need to ensure the slides are updated before continuing.
await CoreUtils.nextTicks(2);
await CoreWait.nextTicks(2);
}
/**

View File

@ -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);

View File

@ -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;

View File

@ -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, [

View File

@ -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;

View File

@ -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 });
}

View File

@ -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;

View File

@ -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,

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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;

View File

@ -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<number> {
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();

View File

@ -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);

View File

@ -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<HTMLElement>('.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`);

View File

@ -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<void> {
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);
}

View File

@ -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<void> {
await CoreUtils.wait(400);
await CoreWait.wait(400);
const scrollElement = await this.content?.getScrollElement();

View File

@ -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 });
}

View File

@ -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<CoreUserToursUserTour> {
const { delay, ...componentOptions } = options;
await CoreUtils.wait(delay ?? 200);
await CoreWait.wait(delay ?? 200);
options.after && await this.waitForUserTour(options.after, options.afterTimeout);

View File

@ -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';
}

View File

@ -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);

View File

@ -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);
}

View File

@ -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> = T & { children: TreeNode<T>[] };
@ -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<void> {
return new Promise(resolve => setTimeout(resolve, milliseconds));
async wait(milliseconds: number): Promise<void> {
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<void>;
waitFor(condition: () => boolean, options: CoreUtilsWaitOptions): CoreCancellablePromise<void>;
waitFor(condition: () => boolean, options: CoreWaitOptions): CoreCancellablePromise<void>;
waitFor(condition: () => boolean, interval: number): CoreCancellablePromise<void>;
waitFor(condition: () => boolean, optionsOrInterval: CoreUtilsWaitOptions | number = {}): CoreCancellablePromise<void> {
waitFor(condition: () => boolean, optionsOrInterval: CoreWaitOptions | number = {}): CoreCancellablePromise<void> {
const options = typeof optionsOrInterval === 'number' ? { interval: optionsOrInterval } : optionsOrInterval;
if (condition()) {
return CoreCancellablePromise.resolve();
}
const startTime = Date.now();
let intervalId: number | undefined;
return new CoreCancellablePromise<void>(
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<void> {
return this.wait(0);
async nextTick(): Promise<void> {
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<void> {
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.

View File

@ -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) => {

View File

@ -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();
};

View File

@ -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<void> {
return CoreWait.wait(0);
}
/**
* Wait until several next ticks.
*
* @param numTicks Number of ticks to wait.
*/
static async nextTicks(numTicks = 0): Promise<void> {
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<void> {
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<void>;
static waitFor(condition: () => boolean, options: CoreWaitOptions): CoreCancellablePromise<void>;
static waitFor(condition: () => boolean, interval: number): CoreCancellablePromise<void>;
static waitFor(condition: () => boolean, optionsOrInterval: CoreWaitOptions | number = {}): CoreCancellablePromise<void> {
const options = typeof optionsOrInterval === 'number' ? { interval: optionsOrInterval } : optionsOrInterval;
if (condition()) {
return CoreCancellablePromise.resolve();
}
const startTime = Date.now();
let intervalId: number | undefined;
return new CoreCancellablePromise<void>(
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;
};

View File

@ -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<void> {
await CoreUtils.nextTick();
await CoreWait.nextTick();
const blockingElements = Array.from(
document.querySelectorAll<HTMLElement>('div.core-loading-container, ion-loading'),

View File

@ -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', {