diff --git a/src/core/classes/delegate-sorted.ts b/src/core/classes/delegate-sorted.ts index d7e81df83..a71aa1677 100644 --- a/src/core/classes/delegate-sorted.ts +++ b/src/core/classes/delegate-sorted.ts @@ -16,7 +16,6 @@ import { BehaviorSubject, Subject } from 'rxjs'; import { CoreEvents } from '@singletons/events'; import { CoreDelegate, CoreDelegateDisplayHandler, CoreDelegateToDisplay } from './delegate'; import { CoreSites } from '@services/sites'; -import { CorePromisedValue } from '@classes/promised-value'; /** * Superclass to help creating sorted delegates. @@ -26,7 +25,6 @@ export class CoreSortedDelegate< HandlerType extends CoreDelegateDisplayHandler> extends CoreDelegate { - protected loaded = false; protected sortedHandlersRxJs: Subject = new BehaviorSubject([]); protected sortedHandlers: DisplayType[] = []; @@ -53,14 +51,14 @@ export class CoreSortedDelegate< * @returns True if handlers are loaded, false otherwise. */ areHandlersLoaded(): boolean { - return this.loaded; + return this.handlersLoaded; } /** * Clear current site handlers. Reserved for core use. */ protected clearSortedHandlers(): void { - this.loaded = false; + this.handlersLoaded = false; this.sortedHandlersRxJs.next([]); this.sortedHandlers = []; } @@ -74,6 +72,13 @@ export class CoreSortedDelegate< return this.sortedHandlers; } + /** + * @inheritdoc + */ + hasHandlers(enabled = false): boolean { + return enabled ? !!this.sortedHandlers.length : !!this.handlers.length; + } + /** * Get the handlers for the current site. * @@ -89,27 +94,15 @@ export class CoreSortedDelegate< * @returns Promise resolved with the handlers. */ async getHandlersWhenLoaded(): Promise { - if (this.loaded) { - return this.sortedHandlers; - } + await this.waitForReady(); - const promisedHandlers = new CorePromisedValue(); - const subscription = this.getHandlersObservable().subscribe((handlers) => { - if (this.loaded) { - subscription?.unsubscribe(); - - // Return main handlers. - promisedHandlers.resolve(handlers); - } - }); - - return promisedHandlers; + return this.sortedHandlers; } /** * Update handlers Data. */ - updateData(): void { + protected updateData(): void { const displayData: DisplayType[] = []; for (const name in this.enabledHandlers) { @@ -125,7 +118,7 @@ export class CoreSortedDelegate< // Sort them by priority. displayData.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0)); - this.loaded = true; + this.handlersLoaded = true; this.sortedHandlersRxJs.next(displayData); this.sortedHandlers = displayData; } diff --git a/src/core/classes/delegate.ts b/src/core/classes/delegate.ts index c18aa061d..cc0675e66 100644 --- a/src/core/classes/delegate.ts +++ b/src/core/classes/delegate.ts @@ -16,6 +16,8 @@ import { CoreSites } from '@services/sites'; import { CoreEvents } from '@singletons/events'; import { CoreSite } from '@classes/sites/site'; import { CoreLogger } from '@singletons/logger'; +import { Subject, BehaviorSubject } from 'rxjs'; +import { CorePromisedValue } from './promised-value'; /** * Superclass to help creating delegates @@ -66,21 +68,14 @@ export class CoreDelegate { protected updatePromises: {[siteId: string]: {[name: string]: Promise}} = {}; /** - * Whether handlers have been initialized. + * Subject to subscribe to handlers changes. */ - protected handlersInitialized = false; + protected handlersUpdated: Subject = new BehaviorSubject(undefined); /** - * Promise to wait for handlers to be initialized. - * - * @returns Promise resolved when handlers are enabled. + * Handlers loaded flag. */ - protected handlersInitPromise: Promise; - - /** - * Function to resolve the handlers init promise. - */ - protected handlersInitResolve!: (enabled: boolean) => void; + protected handlersLoaded = false; /** * Constructor of the Delegate. @@ -90,10 +85,6 @@ export class CoreDelegate { constructor(delegateName: string) { this.logger = CoreLogger.getInstance(delegateName); - this.handlersInitPromise = new Promise((resolve): void => { - this.handlersInitResolve = resolve; - }); - // Update handlers on this cases. CoreEvents.on(CoreEvents.LOGIN, () => this.updateHandlers()); CoreEvents.on(CoreEvents.SITE_UPDATED, () => this.updateHandlers()); @@ -120,6 +111,7 @@ export class CoreDelegate { } /** + * Check if handlers are loaded. * Execute a certain function in a enabled handler. * If the handler isn't found or function isn't defined, call the same function in the default handler. * @@ -216,12 +208,13 @@ export class CoreDelegate { } /** - * Check if the delegate has at least 1 registered handler (not necessarily enabled). + * Returns if the delegate has any handler. * - * @returns If there is at least 1 handler. + * @param enabled Check only enabled handlers or all. + * @returns True if there's any registered handler, false otherwise. */ - hasHandlers(): boolean { - return Object.keys(this.handlers).length > 0; + hasHandlers(enabled = false): boolean { + return enabled ? !!this.enabledHandlers.length : !!this.handlers.length; } /** @@ -324,13 +317,16 @@ export class CoreDelegate { * * @returns Resolved when done. */ - async updateHandlers(): Promise { + protected async updateHandlers(): Promise { + this.handlersLoaded = false; + const enabled = await this.isEnabled(); if (!enabled) { this.logger.debug('Delegate not enabled.'); - this.handlersInitResolve(false); + this.handlersLoaded = true; + this.handlersUpdated.next(); return; } @@ -355,10 +351,10 @@ export class CoreDelegate { // Verify that this call is the last one that was started. if (this.isLastUpdateCall(now)) { - this.handlersInitialized = true; - this.handlersInitResolve(true); - this.updateData(); + + this.handlersLoaded = true; + this.handlersUpdated.next(); } } @@ -366,10 +362,34 @@ export class CoreDelegate { * Update handlers Data. * Override this function to update handlers data. */ - updateData(): void { + protected updateData(): void { // To be overridden. } + /** + * Waits the handlers to be ready. + * + * @returns Resolved when the handlers are ready. + */ + async waitForReady(): Promise { + if (this.handlersLoaded) { + return; + } + + const promise = new CorePromisedValue(); + + const subscription = this.handlersUpdated.subscribe(() => { + if (this.handlersLoaded) { + // Resolve. + promise.resolve(); + + subscription?.unsubscribe(); + } + }); + + return promise; + } + } /** diff --git a/src/core/features/block/services/block-delegate.ts b/src/core/features/block/services/block-delegate.ts index 95501d612..4ec118e67 100644 --- a/src/core/features/block/services/block-delegate.ts +++ b/src/core/features/block/services/block-delegate.ts @@ -212,7 +212,7 @@ export class CoreBlockDelegateService extends CoreDelegate { * Called when there are new block handlers available. Informs anyone who subscribed to the * observable. */ - updateData(): void { + protected updateData(): void { this.blocksUpdateObservable.next(); } diff --git a/src/core/features/course/services/course-options-delegate.ts b/src/core/features/course/services/course-options-delegate.ts index 949f113a0..deac97d58 100644 --- a/src/core/features/course/services/course-options-delegate.ts +++ b/src/core/features/course/services/course-options-delegate.ts @@ -26,6 +26,8 @@ import { import { CoreCourseAccessDataType } from '../constants'; import { Params } from '@angular/router'; import { makeSingleton } from '@singletons'; +import { Subject } from 'rxjs/internal/Subject'; +import { BehaviorSubject } from 'rxjs'; import { CorePromisedValue } from '@classes/promised-value'; import { CORE_COURSES_MY_COURSES_REFRESHED_EVENT } from '@features/courses/constants'; @@ -214,7 +216,8 @@ export interface CoreCourseOptionsMenuHandlerToDisplay { @Injectable( { providedIn: 'root' }) export class CoreCourseOptionsDelegateService extends CoreDelegate { - protected loaded: { [courseId: number]: boolean } = {}; + protected courseHandlersUpdated: { [courseId: number]: Subject } = {}; + protected courseHandlersLoaded: { [courseId: number]: boolean } = {}; protected lastUpdateHandlersForCoursesStart: { [courseId: number]: number; } = {}; @@ -224,7 +227,6 @@ export class CoreCourseOptionsDelegateService extends CoreDelegate; enabledHandlers: CoreCourseOptionsHandler[]; enabledMenuHandlers: CoreCourseOptionsMenuHandler[]; }; @@ -247,7 +249,7 @@ export class CoreCourseOptionsDelegateService extends CoreDelegate { // If the handlers aren't loaded, do not refresh. - if (!this.loaded[courseId]) { + if (!this.courseHandlersLoaded[courseId]) { refresh = false; } @@ -331,21 +333,20 @@ export class CoreCourseOptionsDelegateService extends CoreDelegate(undefined); } else { this.coursesHandlers[courseId].access = accessData; this.coursesHandlers[courseId].navOptions = navOptions; this.coursesHandlers[courseId].admOptions = admOptions; - this.coursesHandlers[courseId].deferred = new CorePromisedValue(); } this.updateHandlersForCourse(courseId, accessData, navOptions, admOptions); } - await this.coursesHandlers[courseId].deferred; + await this.waitCourseHandlersForReady(courseId); } /** @@ -612,7 +613,7 @@ export class CoreCourseOptionsDelegateService extends CoreDelegate { + this.courseHandlersLoaded[courseId] = false; + const promises: Promise[] = []; const enabledForCourse: CoreCourseOptionsHandler[] = []; const enabledForCourseMenu: CoreCourseOptionsMenuHandler[] = []; @@ -675,13 +678,38 @@ export class CoreCourseOptionsDelegateService extends CoreDelegate { + if (this.courseHandlersLoaded[courseId]) { + return; + } + + const promise = new CorePromisedValue(); + + const subscription = this.courseHandlersUpdated[courseId].subscribe(() => { + if (this.courseHandlersLoaded[courseId]) { + // Resolve. + promise.resolve(); + + subscription?.unsubscribe(); + } + }); + + return promise; + } + } export const CoreCourseOptionsDelegate = makeSingleton(CoreCourseOptionsDelegateService); diff --git a/src/core/features/filter/services/filter-delegate.ts b/src/core/features/filter/services/filter-delegate.ts index 124b60600..3092eaeb3 100644 --- a/src/core/features/filter/services/filter-delegate.ts +++ b/src/core/features/filter/services/filter-delegate.ts @@ -112,9 +112,9 @@ export class CoreFilterDelegateService extends CoreDelegate { skipFilters?: string[], siteId?: string, ): Promise { - // Wait for filters to be initialized. - const enabled = await this.handlersInitPromise; + await this.waitForReady(); + const enabled = this.hasHandlers(true); if (!enabled) { // No enabled filters, return the text. return text; @@ -201,7 +201,8 @@ export class CoreFilterDelegateService extends CoreDelegate { ): Promise { // Wait for filters to be initialized. - const enabled = await this.handlersInitPromise; + await this.waitForReady(); + const enabled = this.hasHandlers(true); if (!enabled) { return; } @@ -276,7 +277,8 @@ export class CoreFilterDelegateService extends CoreDelegate { */ async shouldBeApplied(filters: CoreFilterFilter[], options: CoreFilterFormatTextOptions, site?: CoreSite): Promise { // Wait for filters to be initialized. - const enabled = await this.handlersInitPromise; + await this.waitForReady(); + const enabled = this.hasHandlers(true); if (!enabled) { return false; } diff --git a/src/core/services/tests/plugin-file-delegate.test.ts b/src/core/services/tests/plugin-file-delegate.test.ts index 9fafa1324..1db9bdd0c 100644 --- a/src/core/services/tests/plugin-file-delegate.test.ts +++ b/src/core/services/tests/plugin-file-delegate.test.ts @@ -16,6 +16,7 @@ import { mock, mockSingleton } from '@/testing/utils'; import { CoreSite } from '@classes/sites/site'; import { CorePluginFileDelegateService, CorePluginFileHandler } from '@services/plugin-file-delegate'; import { CoreSites } from '@services/sites'; +import { CoreEvents } from '@singletons/events'; import { CoreUrl } from '@singletons/url'; describe('CorePluginFileDelegate', () => { @@ -32,7 +33,8 @@ describe('CorePluginFileDelegate', () => { pluginFileDelegate = new CorePluginFileDelegateService(); pluginFileDelegate.registerHandler(new ModFooRevisionHandler()); - await pluginFileDelegate.updateHandlers(); + CoreEvents.trigger(CoreEvents.LOGIN, { siteId: '42' }, '42'); + await pluginFileDelegate.waitForReady(); }); it('removes revision from a URL', () => {