diff --git a/src/addons/blog/pages/index/index.ts b/src/addons/blog/pages/index/index.ts index 1bdab2551..c1cc8e4fc 100644 --- a/src/addons/blog/pages/index/index.ts +++ b/src/addons/blog/pages/index/index.ts @@ -29,7 +29,6 @@ import { AddonBlogOffline, AddonBlogOfflineEntry } from '@addons/blog/services/b import { AddonBlogSync } from '@addons/blog/services/blog-sync'; import { Component, computed, OnDestroy, OnInit, signal } from '@angular/core'; import { CoreComments } from '@features/comments/services/comments'; -import { CoreMainMenuDeepLinkManager } from '@features/mainmenu/classes/deep-link-manager'; import { CoreTag } from '@features/tag/services/tag'; import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; import { CoreNavigator } from '@services/navigator'; @@ -202,8 +201,7 @@ export class AddonBlogIndexPage implements OnInit, OnDestroy { this.commentsEnabled = CoreComments.areCommentsEnabledInSite(); this.tagsEnabled = CoreTag.areTagsAvailableInSite(); - const deepLinkManager = new CoreMainMenuDeepLinkManager(); - deepLinkManager.treatLink(); + CoreSites.loginNavigationFinished(); await this.fetchEntries(false, false, true); this.optionsAvailable = await AddonBlog.isEditingEnabled(); diff --git a/src/addons/calendar/pages/index/index.ts b/src/addons/calendar/pages/index/index.ts index 7ee837bf6..7bfa6e2d6 100644 --- a/src/addons/calendar/pages/index/index.ts +++ b/src/addons/calendar/pages/index/index.ts @@ -30,7 +30,6 @@ import { AddonCalendarCalendarComponent } from '../../components/calendar/calend import { AddonCalendarUpcomingEventsComponent } from '../../components/upcoming-events/upcoming-events'; import { CoreNavigator } from '@services/navigator'; import { CoreConstants } from '@/core/constants'; -import { CoreMainMenuDeepLinkManager } from '@features/mainmenu/classes/deep-link-manager'; import { CoreModals } from '@services/modals'; /** @@ -178,8 +177,7 @@ export class AddonCalendarIndexPage implements OnInit, OnDestroy { } }); - const deepLinkManager = new CoreMainMenuDeepLinkManager(); - deepLinkManager.treatLink(); + CoreSites.loginNavigationFinished(); } /** diff --git a/src/addons/messages/pages/discussions-35/discussions.ts b/src/addons/messages/pages/discussions-35/discussions.ts index c44a252ad..b6375820b 100644 --- a/src/addons/messages/pages/discussions-35/discussions.ts +++ b/src/addons/messages/pages/discussions-35/discussions.ts @@ -30,7 +30,6 @@ import { Subscription } from 'rxjs'; import { Translate } from '@singletons'; import { CoreNavigator } from '@services/navigator'; import { CoreScreen } from '@services/screen'; -import { CoreMainMenuDeepLinkManager } from '@features/mainmenu/classes/deep-link-manager'; import { CorePlatform } from '@services/platform'; import { CoreSplitViewComponent } from '@components/split-view/split-view'; import { CoreKeyboard } from '@singletons/keyboard'; @@ -145,8 +144,6 @@ export class AddonMessagesDiscussions35Page implements OnInit, OnDestroy { this.discussionUserId = CoreNavigator.getRouteNumberParam('userId', { params }) ?? this.discussionUserId; }); - const deepLinkManager = new CoreMainMenuDeepLinkManager(); - await this.fetchData(); if (!this.discussionUserId && this.discussions.length > 0 && CoreScreen.isTablet && this.discussions[0].message) { @@ -154,8 +151,8 @@ export class AddonMessagesDiscussions35Page implements OnInit, OnDestroy { await this.gotoDiscussion(this.discussions[0].message.user); } - // Treat deep link now that the conversation route has been loaded if needed. - deepLinkManager.treatLink(); + // Mark login navigation finished now that the conversation route has been loaded if needed. + CoreSites.loginNavigationFinished(); } /** diff --git a/src/addons/messages/pages/group-conversations/group-conversations.ts b/src/addons/messages/pages/group-conversations/group-conversations.ts index 20fbedc11..03f1eca6e 100644 --- a/src/addons/messages/pages/group-conversations/group-conversations.ts +++ b/src/addons/messages/pages/group-conversations/group-conversations.ts @@ -38,7 +38,6 @@ import { ActivatedRoute, Params } from '@angular/router'; import { CoreUtils } from '@services/utils/utils'; import { CoreNavigator } from '@services/navigator'; import { CoreScreen } from '@services/screen'; -import { CoreMainMenuDeepLinkManager } from '@features/mainmenu/classes/deep-link-manager'; import { CorePlatform } from '@services/platform'; import { CoreSplitViewComponent } from '@components/split-view/split-view'; @@ -312,8 +311,6 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { } }); - const deepLinkManager = new CoreMainMenuDeepLinkManager(); - await this.fetchData(); if (!this.selectedConversationId && !this.selectedUserId && CoreScreen.isTablet) { @@ -326,8 +323,8 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { } } - // Treat deep link now that the conversation route has been loaded if needed. - deepLinkManager.treatLink(); + // Mark login navigation finished now that the conversation route has been loaded if needed. + CoreSites.loginNavigationFinished(); } /** diff --git a/src/addons/notifications/pages/list/list.ts b/src/addons/notifications/pages/list/list.ts index 52ab8136b..79602218e 100644 --- a/src/addons/notifications/pages/list/list.ts +++ b/src/addons/notifications/pages/list/list.ts @@ -26,7 +26,6 @@ import { CoreSplitViewComponent } from '@components/split-view/split-view'; import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker'; import { CorePushNotificationsDelegate } from '@features/pushnotifications/services/push-delegate'; import { CoreSites } from '@services/sites'; -import { CoreMainMenuDeepLinkManager } from '@features/mainmenu/classes/deep-link-manager'; import { CoreTimeUtils } from '@services/utils/time'; import { AddonNotificationsNotificationsSource } from '@addons/notifications/classes/notifications-source'; import { CoreListItemsManager } from '@classes/items-management/list-items-manager'; @@ -130,8 +129,7 @@ export class AddonNotificationsListPage implements AfterViewInit, OnDestroy { this.loadMarkAllAsReadButton(); }); - const deepLinkManager = new CoreMainMenuDeepLinkManager(); - deepLinkManager.treatLink(); + CoreSites.loginNavigationFinished(); } /** diff --git a/src/core/classes/queue-runner.ts b/src/core/classes/queue-runner.ts index 6795af7ad..ff0fcc69b 100644 --- a/src/core/classes/queue-runner.ts +++ b/src/core/classes/queue-runner.ts @@ -39,6 +39,11 @@ export type CoreQueueRunnerItem = { * Deferred with a promise resolved/rejected with the result of the function. */ deferred: CorePromisedValue; + + /** + * Item's priority. Only used if usePriority=true. + */ + priority: number; }; /** @@ -49,6 +54,12 @@ export type CoreQueueRunnerAddOptions = { * Whether to allow having multiple entries with same ID in the queue. */ allowRepeated?: boolean; + + /** + * If usePriority=true, the priority of the item. Higher priority means it will be executed first. + * Please notice that the first item is always run immediately, so it's not affected by the priority. + */ + priority?: number; }; /** @@ -60,7 +71,13 @@ export class CoreQueueRunner { protected orderedQueue: CoreQueueRunnerItem[] = []; protected numberRunning = 0; - constructor(protected maxParallel: number = 1) { } + /** + * Constructor. + * + * @param maxParallel Max number of parallel executions. + * @param usePriority If true, the queue will be ordered by priority. + */ + constructor(protected maxParallel: number = 1, protected usePriority = false) { } /** * Get unique ID. @@ -147,10 +164,14 @@ export class CoreQueueRunner { id, fn, deferred: new CorePromisedValue(), + priority: options.priority ?? 0, }; this.queue[id] = item; this.orderedQueue.push(item); + if (this.usePriority) { + this.orderedQueue.sort((a, b) => b.priority - a.priority); + } // Process next item if we haven't reached the max yet. this.processNextItem(); diff --git a/src/core/features/courses/pages/my/my.ts b/src/core/features/courses/pages/my/my.ts index f4f1ff63b..370e9b244 100644 --- a/src/core/features/courses/pages/my/my.ts +++ b/src/core/features/courses/pages/my/my.ts @@ -21,7 +21,6 @@ import { CoreBlockComponent } from '@features/block/components/block/block'; import { CoreBlockDelegate } from '@features/block/services/block-delegate'; import { CoreCourseBlock } from '@features/course/services/course'; import { CoreCoursesDashboard, CoreCoursesDashboardProvider } from '@features/courses/services/dashboard'; -import { CoreMainMenuDeepLinkManager } from '@features/mainmenu/classes/deep-link-manager'; import { CoreSites } from '@services/sites'; import { CoreDomUtils } from '@services/utils/dom'; import { CoreUtils } from '@services/utils/utils'; @@ -97,8 +96,7 @@ export class CoreCoursesMyPage implements OnInit, OnDestroy, AsyncDirective { async ngOnInit(): Promise { this.downloadCoursesEnabled = !CoreCourses.isDownloadCoursesDisabledInSite(); - const deepLinkManager = new CoreMainMenuDeepLinkManager(); - deepLinkManager.treatLink(); + CoreSites.loginNavigationFinished(); await this.loadSiteName(); diff --git a/src/core/features/mainmenu/classes/deep-link-manager.ts b/src/core/features/mainmenu/classes/deep-link-manager.ts index 2dc0ceb6e..9cb68bc0b 100644 --- a/src/core/features/mainmenu/classes/deep-link-manager.ts +++ b/src/core/features/mainmenu/classes/deep-link-manager.ts @@ -50,18 +50,18 @@ export class CoreMainMenuDeepLinkManager { /** * Treat a deep link if there's any to treat. */ - treatLink(): void { - if (!this.pendingRedirect) { + async treatLink(): Promise { + const pendingRedirect = this.pendingRedirect; + if (!pendingRedirect) { return; } - if (this.pendingRedirect.redirectPath) { - this.treatPath(this.pendingRedirect.redirectPath, this.pendingRedirect.redirectOptions); - } else if (this.pendingRedirect.urlToOpen) { - this.treatUrlToOpen(this.pendingRedirect.urlToOpen); - } - delete this.pendingRedirect; + if (pendingRedirect.redirectPath) { + await this.treatPath(pendingRedirect.redirectPath, pendingRedirect.redirectOptions); + } else if (pendingRedirect.urlToOpen) { + await this.treatUrlToOpen(pendingRedirect.urlToOpen); + } } /** @@ -70,18 +70,18 @@ export class CoreMainMenuDeepLinkManager { * @param path Path. * @param navOptions Navigation options. */ - protected treatPath(path: string, navOptions: CoreNavigationOptions = {}): void { + protected async treatPath(path: string, navOptions: CoreNavigationOptions = {}): Promise { const params = navOptions.params; const coursePathMatches = path.match(/^course\/(\d+)\/?$/); if (coursePathMatches) { if (!params?.course) { - CoreCourseHelper.getAndOpenCourse(Number(coursePathMatches[1]), params); + await CoreCourseHelper.getAndOpenCourse(Number(coursePathMatches[1]), params); } else { - CoreCourse.openCourse(params.course, navOptions); + await CoreCourse.openCourse(params.course, navOptions); } } else { - CoreNavigator.navigateToSitePath(path, { + await CoreNavigator.navigateToSitePath(path, { ...navOptions, preferCurrentTab: false, }); @@ -96,7 +96,7 @@ export class CoreMainMenuDeepLinkManager { protected async treatUrlToOpen(url: string): Promise { const action = await CoreContentLinksHelper.getFirstValidActionFor(url); if (action?.sites?.[0]) { - action.action(action.sites[0]); + await action.action(action.sites[0]); } } diff --git a/src/core/features/mainmenu/pages/home/home.ts b/src/core/features/mainmenu/pages/home/home.ts index ac827d54f..ab928b4c7 100644 --- a/src/core/features/mainmenu/pages/home/home.ts +++ b/src/core/features/mainmenu/pages/home/home.ts @@ -21,7 +21,6 @@ import { CoreTabsOutletComponent, CoreTabsOutletTab } from '@components/tabs-out import { CoreMainMenuHomeDelegate, CoreMainMenuHomeHandlerToDisplay } from '../../services/home-delegate'; import { CoreUtils } from '@services/utils/utils'; import { CoreMainMenuHomeHandlerService } from '@features/mainmenu/services/handlers/mainmenu'; -import { CoreMainMenuDeepLinkManager } from '@features/mainmenu/classes/deep-link-manager'; /** * Page that displays the Home. @@ -40,14 +39,11 @@ export class CoreMainMenuHomePage implements OnInit { protected subscription?: Subscription; protected updateSiteObserver?: CoreEventObserver; - protected deepLinkManager?: CoreMainMenuDeepLinkManager; /** * @inheritdoc */ async ngOnInit(): Promise { - this.deepLinkManager = new CoreMainMenuDeepLinkManager(); - await this.loadSiteName(); this.subscription = CoreMainMenuHomeDelegate.getHandlersObservable().subscribe((handlers) => { @@ -108,7 +104,7 @@ export class CoreMainMenuHomePage implements OnInit { * Tab was selected. */ tabSelected(): void { - this.deepLinkManager?.treatLink(); + CoreSites.loginNavigationFinished(); } /** diff --git a/src/core/features/mainmenu/pages/menu/menu.ts b/src/core/features/mainmenu/pages/menu/menu.ts index 1c62bf7ad..d2c43a7f0 100644 --- a/src/core/features/mainmenu/pages/menu/menu.ts +++ b/src/core/features/mainmenu/pages/menu/menu.ts @@ -23,7 +23,7 @@ import { CoreMainMenuDelegate, CoreMainMenuHandlerToDisplay } from '../../servic import { Router } from '@singletons'; import { CoreUtils } from '@services/utils/utils'; import { CoreAriaRoleTab, CoreAriaRoleTabFindable } from '@classes/aria-role-tab'; -import { CoreNavigationOptions, CoreNavigator } from '@services/navigator'; +import { CoreNavigator } from '@services/navigator'; import { filter } from 'rxjs/operators'; import { NavigationEnd } from '@angular/router'; import { trigger, state, style, transition, animate } from '@angular/animations'; @@ -32,6 +32,7 @@ import { CoreDom } from '@singletons/dom'; import { CoreLogger } from '@singletons/logger'; import { CorePlatform } from '@services/platform'; import { CoreWait } from '@singletons/wait'; +import { CoreMainMenuDeepLinkManager } from '@features/mainmenu/classes/deep-link-manager'; const ANIMATION_DURATION = 500; @@ -83,9 +84,6 @@ export class CoreMainMenuPage implements OnInit, OnDestroy { protected backButtonFunction: (event: BackButtonEvent) => void; protected selectHistory: string[] = []; protected firstSelectedTab?: string; - protected urlToOpen?: string; - protected redirectPath?: string; - protected redirectOptions?: CoreNavigationOptions; protected logger: CoreLogger; @ViewChild('mainTabs') mainTabs?: IonTabs; @@ -111,9 +109,16 @@ export class CoreMainMenuPage implements OnInit, OnDestroy { */ async ngOnInit(): Promise { this.showTabs = true; - this.urlToOpen = CoreNavigator.getRouteParam('urlToOpen'); - this.redirectPath = CoreNavigator.getRouteParam('redirectPath'); - this.redirectOptions = CoreNavigator.getRouteParam('redirectOptions'); + + const deepLinkManager = new CoreMainMenuDeepLinkManager(); + + // Treat the deep link (if any) when the login navigation finishes. + CoreSites.runAfterLoginNavigation({ + priority: 800, + callback: async () => { + await deepLinkManager.treatLink(); + }, + }); this.isMainScreen = !this.mainTabs?.outlet.canGoBack(); this.updateVisibility(); @@ -213,12 +218,7 @@ export class CoreMainMenuPage implements OnInit, OnDestroy { // Use navigate instead of mainTabs.select to be able to pass page params. CoreNavigator.navigateToSitePath(tabPage, { preferCurrentTab: false, - params: { - urlToOpen: this.urlToOpen, - redirectPath: this.redirectPath, - redirectOptions: this.redirectOptions, - ...tabPageParams, - }, + params: tabPageParams, }); } } diff --git a/src/core/features/mainmenu/pages/more/more.ts b/src/core/features/mainmenu/pages/more/more.ts index 5571d74d8..d20ae7e31 100644 --- a/src/core/features/mainmenu/pages/more/more.ts +++ b/src/core/features/mainmenu/pages/more/more.ts @@ -22,7 +22,6 @@ import { CoreMainMenu, CoreMainMenuCustomItem } from '../../services/mainmenu'; import { CoreEventObserver, CoreEvents } from '@singletons/events'; import { CoreNavigator } from '@services/navigator'; import { Translate } from '@singletons'; -import { CoreMainMenuDeepLinkManager } from '@features/mainmenu/classes/deep-link-manager'; import { CoreDom } from '@singletons/dom'; import { CoreViewer } from '@features/viewer/services/viewer'; @@ -75,8 +74,7 @@ export class CoreMainMenuMorePage implements OnInit, OnDestroy { this.initHandlers(); }); - const deepLinkManager = new CoreMainMenuDeepLinkManager(); - deepLinkManager.treatLink(); + CoreSites.loginNavigationFinished(); } /** diff --git a/src/core/features/tag/pages/search/search.ts b/src/core/features/tag/pages/search/search.ts index 6cc3147de..7387dd3a8 100644 --- a/src/core/features/tag/pages/search/search.ts +++ b/src/core/features/tag/pages/search/search.ts @@ -21,10 +21,10 @@ import { CoreTagCloud, CoreTagCollection, CoreTagCloudTag, CoreTag } from '@feat import { Translate } from '@singletons'; import { CoreContentLinksHelper } from '@features/contentlinks/services/contentlinks-helper'; import { CoreNavigator } from '@services/navigator'; -import { CoreMainMenuDeepLinkManager } from '@features/mainmenu/classes/deep-link-manager'; import { CoreTime } from '@singletons/time'; import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; import { CoreKeyboard } from '@singletons/keyboard'; +import { CoreSites } from '@services/sites'; /** * Page that displays most used tags and allows searching. @@ -65,8 +65,7 @@ export class CoreTagSearchPage implements OnInit { this.collectionId = CoreNavigator.getRouteNumberParam('collectionId') || 0; this.query = CoreNavigator.getRouteParam('query') || ''; - const deepLinkManager = new CoreMainMenuDeepLinkManager(); - deepLinkManager.treatLink(); + CoreSites.loginNavigationFinished(); this.fetchData().finally(() => { this.loaded = true; diff --git a/src/core/services/sites.ts b/src/core/services/sites.ts index 21d7901f3..f8ffdc56e 100644 --- a/src/core/services/sites.ts +++ b/src/core/services/sites.ts @@ -67,6 +67,7 @@ import { firstValueFrom } from 'rxjs'; import { CoreHTMLClasses } from '@singletons/html-classes'; import { CoreSiteErrorDebug } from '@classes/errors/siteerror'; import { CoreErrorHelper } from './error-helper'; +import { CoreQueueRunner } from '@classes/queue-runner'; export const CORE_SITE_SCHEMAS = new InjectionToken('CORE_SITE_SCHEMAS'); export const CORE_SITE_CURRENT_SITE_ID_CONFIG = 'current_site_id'; @@ -96,6 +97,11 @@ export class CoreSitesProvider { protected schemasTables: Record>> = {}; protected sitesTable = asyncInstance>(); + // Variables to run code after login navigation. + protected isLoginNavigationFinished = false; + protected afterLoginNavigationQueue: CoreSitesAfterLoginNavigationProcess[] = []; + protected afterLoginNavigationQueueRunner = new CoreQueueRunner(1, true); + constructor(@Optional() @Inject(CORE_SITE_SCHEMAS) siteSchemas: CoreSiteSchema[][] | null) { this.logger = CoreLogger.getInstance('CoreSitesProvider'); this.siteSchemas = (siteSchemas ?? []).flat().reduce( @@ -1462,6 +1468,8 @@ export class CoreSitesProvider { const siteId = this.currentSite.getId(); this.currentSite = undefined; + this.isLoginNavigationFinished = false; + this.afterLoginNavigationQueue = []; if (options.forceLogout || (siteConfig && siteConfig.tool_mobile_forcelogout == '1')) { promises.push(this.setSiteLoggedOut(siteId)); @@ -2168,6 +2176,47 @@ export class CoreSitesProvider { ); } + /** + * Run some code when the login navigation is finished. Login navigation finishes when the proper main menu page + * has loaded. + * If not logged in or the login navigation is already finished, the callback will run immediately (waiting for currently + * running processes to finish). + * Otherwise, the process will be added to a queue and will run once the login navigation is finished. + * + * @param data Process data. + */ + runAfterLoginNavigation(data: CoreSitesAfterLoginNavigationProcess): void { + if (!this.isLoggedIn() || this.isLoginNavigationFinished) { + this.afterLoginNavigationQueueRunner.run(data.callback, { priority: data.priority }); + + return; + } + + this.afterLoginNavigationQueue.push(data); + + // Sort the list by priority. The queue runner also uses priority, but the first run is always executed immediately + // so it's important to always pass the highest priority process first. + this.afterLoginNavigationQueue.sort((a, b) => b.priority - a.priority); + } + + /** + * Notify that the login navigation is finished. This function should only be used by main menu pages. + */ + loginNavigationFinished(): void { + if (this.isLoginNavigationFinished) { + // Already finished, nothing else to do. + return; + } + + this.isLoginNavigationFinished = true; + + // Run the processes in the queue. + this.afterLoginNavigationQueue.forEach(data => { + this.afterLoginNavigationQueueRunner.run(data.callback, { priority: data.priority }); + }); + this.afterLoginNavigationQueue = []; + } + } export const CoreSites = makeSingleton(CoreSitesProvider); @@ -2392,3 +2441,11 @@ export type CoreSitesLogoutOptions = { forceLogout?: boolean; // If true, site will be marked as logged out, no matter the value tool_mobile_forcelogout. removeAccount?: boolean; // If true, site will be removed too after logout. }; + +/** + * Process to run after login navigation finishes. + */ +export type CoreSitesAfterLoginNavigationProcess = { + priority: number; + callback: () => Promise; +};