MOBILE-3320 routing: Refactor NavHelper

main
Noel De Martin 2021-01-14 12:58:51 +01:00
parent 554a6d7717
commit 09adbb7fbf
37 changed files with 629 additions and 553 deletions

View File

@ -19,7 +19,7 @@ import { CoreTimeUtils } from '@services/utils/time';
import { CoreDomUtils } from '@services/utils/dom'; import { CoreDomUtils } from '@services/utils/dom';
import { CoreSites } from '@services/sites'; import { CoreSites } from '@services/sites';
import { CoreUtils } from '@services/utils/utils'; import { CoreUtils } from '@services/utils/utils';
import { CoreNavHelper } from '@services/nav-helper'; import { CoreNavigator } from '@services/navigator';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
// @todo import { CoreSplitViewComponent } from '@components/split-view/split-view'; // @todo import { CoreSplitViewComponent } from '@components/split-view/split-view';
@ -107,7 +107,7 @@ export class AddonBadgesUserBadgesPage implements OnInit {
const params = { courseId: this.courseId, userId: this.userId, badgeHash: badgeHash }; const params = { courseId: this.courseId, userId: this.userId, badgeHash: badgeHash };
// @todo use splitview. // @todo use splitview.
// this.splitviewCtrl.push('AddonBadgesIssuedBadgePage', params); // this.splitviewCtrl.push('AddonBadgesIssuedBadgePage', params);
CoreNavHelper.instance.goInSite('/badges/issue', params); CoreNavigator.instance.navigateToSitePath('/badges/issue', { params });
} }
} }

View File

@ -16,7 +16,7 @@ import { Injectable } from '@angular/core';
import { Params } from '@angular/router'; import { Params } from '@angular/router';
import { CoreContentLinksHandlerBase } from '@features/contentlinks/classes/base-handler'; import { CoreContentLinksHandlerBase } from '@features/contentlinks/classes/base-handler';
import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate'; import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate';
import { CoreNavHelper } from '@services/nav-helper'; import { CoreNavigator } from '@services/navigator';
import { makeSingleton } from '@singletons'; import { makeSingleton } from '@singletons';
import { AddonBadges } from '../badges'; import { AddonBadges } from '../badges';
@ -43,10 +43,12 @@ export class AddonBadgesBadgeLinkHandlerService extends CoreContentLinksHandlerB
return [{ return [{
action: (siteId: string): void => { action: (siteId: string): void => {
CoreNavHelper.instance.goInSite( CoreNavigator.instance.navigateToSitePath(
'/badges/issue', '/badges/issue',
{ courseId: 0, badgeHash: params.hash }, {
siteId, siteId,
params: { courseId: 0, badgeHash: params.hash },
},
); );
}, },
}]; }];

View File

@ -15,7 +15,7 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { CoreContentLinksHandlerBase } from '@features/contentlinks/classes/base-handler'; import { CoreContentLinksHandlerBase } from '@features/contentlinks/classes/base-handler';
import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate'; import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate';
import { CoreNavHelper } from '@services/nav-helper'; import { CoreNavigator } from '@services/navigator';
import { makeSingleton } from '@singletons'; import { makeSingleton } from '@singletons';
import { AddonBadges } from '../badges'; import { AddonBadges } from '../badges';
@ -37,7 +37,7 @@ export class AddonBadgesMyBadgesLinkHandlerService extends CoreContentLinksHandl
getActions(): CoreContentLinksAction[] { getActions(): CoreContentLinksAction[] {
return [{ return [{
action: (siteId: string): void => { action: (siteId: string): void => {
CoreNavHelper.instance.goInSite('/badges/user', {}, siteId); CoreNavigator.instance.navigateToSitePath('/badges/user', { siteId });
}, },
}]; }];
} }

View File

@ -19,7 +19,7 @@ import { CorePushNotificationsClickHandler } from '@features/pushnotifications/s
import { AddonBadges } from '../badges'; import { AddonBadges } from '../badges';
import { makeSingleton } from '@singletons'; import { makeSingleton } from '@singletons';
import { CorePushNotificationsNotificationBasicData } from '@features/pushnotifications/services/pushnotifications'; import { CorePushNotificationsNotificationBasicData } from '@features/pushnotifications/services/pushnotifications';
import { CoreNavHelper } from '@services/nav-helper'; import { CoreNavigator } from '@services/navigator';
/** /**
* Handler for badges push notifications clicks. * Handler for badges push notifications clicks.
@ -59,7 +59,12 @@ export class AddonBadgesPushClickHandlerService implements CorePushNotifications
if (data.hash) { if (data.hash) {
// We have the hash, open the badge directly. // We have the hash, open the badge directly.
return CoreNavHelper.instance.goInSite('/badges/issue', { courseId: 0, badgeHash: data.hash }, notification.site); await CoreNavigator.instance.navigateToSitePath('/badges/issue', {
siteId: notification.site,
params: { courseId: 0, badgeHash: data.hash },
});
return;
} }
// No hash, open the list of user badges. // No hash, open the list of user badges.
@ -71,7 +76,7 @@ export class AddonBadgesPushClickHandlerService implements CorePushNotifications
), ),
); );
await CoreNavHelper.instance.goInSite('/badges/user', {}, notification.site); await CoreNavigator.instance.navigateToSitePath('/badges/user', { siteId: notification.site });
} }
} }

View File

@ -16,7 +16,7 @@ import { Injectable } from '@angular/core';
import { CoreCourseUserAdminOrNavOptionIndexed } from '@features/courses/services/courses'; import { CoreCourseUserAdminOrNavOptionIndexed } from '@features/courses/services/courses';
import { CoreUserProfile } from '@features/user/services/user'; import { CoreUserProfile } from '@features/user/services/user';
import { CoreUserDelegateService, CoreUserProfileHandler, CoreUserProfileHandlerData } from '@features/user/services/user-delegate'; import { CoreUserDelegateService, CoreUserProfileHandler, CoreUserProfileHandlerData } from '@features/user/services/user-delegate';
import { CoreNavHelper } from '@services/nav-helper'; import { CoreNavigator } from '@services/navigator';
import { makeSingleton } from '@singletons'; import { makeSingleton } from '@singletons';
import { AddonBadges } from '../badges'; import { AddonBadges } from '../badges';
@ -72,7 +72,10 @@ export class AddonBadgesUserHandlerService implements CoreUserProfileHandler {
action: (event, user, courseId): void => { action: (event, user, courseId): void => {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
CoreNavHelper.instance.goInSite('/badges/user', { courseId: courseId || 0, userId: user.id }); CoreNavigator.instance.navigateToSitePath(
'/badges/user',
{ params: { courseId: courseId || 0, userId: user.id } },
);
}, },
}; };
} }

View File

@ -145,7 +145,7 @@ export class CoreUserAvatarComponent implements OnInit, OnChanges, OnDestroy {
// @todo Decide which navCtrl to use. If this component is inside a split view, use the split view's master nav. // @todo Decide which navCtrl to use. If this component is inside a split view, use the split view's master nav.
this.navCtrl.navigateForward(['user'], { this.navCtrl.navigateForward(['user'], {
relativeTo: this.route, relativeTo: this.route,
queryParams: CoreObject.removeUndefined({ queryParams: CoreObject.withoutEmpty({
userId: this.userId, userId: this.userId,
courseId: this.courseId, courseId: this.courseId,
}), }),

View File

@ -15,7 +15,7 @@
import { Directive, Input, OnInit, ElementRef } from '@angular/core'; import { Directive, Input, OnInit, ElementRef } from '@angular/core';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { NavController } from '@ionic/angular'; import { NavController } from '@ionic/angular';
import { CoreNavHelper } from '@services/nav-helper'; import { CoreNavigator } from '@services/navigator';
import { CoreObject } from '@singletons/object'; import { CoreObject } from '@singletons/object';
@ -54,10 +54,12 @@ export class CoreUserLinkDirective implements OnInit {
event.stopPropagation(); event.stopPropagation();
// @todo If this directive is inside a split view, use the split view's master nav. // @todo If this directive is inside a split view, use the split view's master nav.
CoreNavHelper.instance.goInCurrentMainMenuTab('user', CoreObject.removeUndefined({ CoreNavigator.instance.navigateToSitePath('user', {
userId: this.userId, params: CoreObject.withoutEmpty({
courseId: this.courseId, userId: this.userId,
})); courseId: this.courseId,
}),
});
}); });
} }

View File

@ -14,7 +14,7 @@
import { OnInit, Component } from '@angular/core'; import { OnInit, Component } from '@angular/core';
import { CoreBlockBaseComponent } from '../../classes/base-block-component'; import { CoreBlockBaseComponent } from '../../classes/base-block-component';
import { CoreNavHelper } from '@services/nav-helper'; import { CoreNavigator } from '@services/navigator';
/** /**
* Component to render blocks with only a title and link. * Component to render blocks with only a title and link.
@ -24,7 +24,7 @@ import { CoreNavHelper } from '@services/nav-helper';
templateUrl: 'core-block-only-title.html', templateUrl: 'core-block-only-title.html',
styleUrls: ['only-title-block.scss'], styleUrls: ['only-title-block.scss'],
}) })
export class CoreBlockOnlyTitleComponent extends CoreBlockBaseComponent implements OnInit { export class CoreBlockOnlyTitleComponent extends CoreBlockBaseComponent implements OnInit {
constructor() { constructor() {
super('CoreBlockOnlyTitleComponent'); super('CoreBlockOnlyTitleComponent');
@ -43,7 +43,8 @@ export class CoreBlockOnlyTitleComponent extends CoreBlockBaseComponent impleme
* Go to the block page. * Go to the block page.
*/ */
gotoBlock(): void { gotoBlock(): void {
CoreNavHelper.instance.goInSite(this.link!, this.linkParams!, undefined, true); // @todo test that this is working properly.
CoreNavigator.instance.navigateToSitePath(this.link!, { params: this.linkParams });
} }
} }

View File

@ -16,7 +16,6 @@ import { CoreContentLinksHandlerBase } from './base-handler';
import { Translate } from '@singletons'; import { Translate } from '@singletons';
import { Params } from '@angular/router'; import { Params } from '@angular/router';
import { CoreContentLinksAction } from '../services/contentlinks-delegate'; import { CoreContentLinksAction } from '../services/contentlinks-delegate';
import { CoreNavHelper } from '@services/nav-helper';
/** /**
* Handler to handle URLs pointing to a list of a certain type of modules. * Handler to handle URLs pointing to a list of a certain type of modules.
@ -56,14 +55,16 @@ export class CoreContentLinksModuleListHandler extends CoreContentLinksHandlerBa
getActions(siteIds: string[], url: string, params: Params): CoreContentLinksAction[] | Promise<CoreContentLinksAction[]> { getActions(siteIds: string[], url: string, params: Params): CoreContentLinksAction[] | Promise<CoreContentLinksAction[]> {
return [{ return [{
// eslint-disable-next-line @typescript-eslint/no-unused-vars
action: (siteId): void => { action: (siteId): void => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const stateParams = { const stateParams = {
courseId: params.id, courseId: params.id,
modName: this.modName, modName: this.modName,
title: this.title || Translate.instance.instant('addon.mod_' + this.modName + '.modulenameplural'), title: this.title || Translate.instance.instant('addon.mod_' + this.modName + '.modulenameplural'),
}; };
CoreNavHelper.instance.goInSite('CoreCourseListModTypePage @todo', stateParams, siteId); // @todo CoreNavigator.instance.goInSite('CoreCourseListModTypePage', stateParams, siteId);
}, },
}]; }];
} }

View File

@ -21,7 +21,7 @@ import { CoreContentLinksAction } from '../../services/contentlinks-delegate';
import { CoreContentLinksHelper } from '../../services/contentlinks-helper'; import { CoreContentLinksHelper } from '../../services/contentlinks-helper';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { CoreError } from '@classes/errors/error'; import { CoreError } from '@classes/errors/error';
import { CoreNavHelper } from '@services/nav-helper'; import { CoreNavigator } from '@services/navigator';
/** /**
* Page to display the list of sites to choose one to perform a content link action. * Page to display the list of sites to choose one to perform a content link action.
@ -102,7 +102,7 @@ export class CoreContentLinksChooseSitePage implements OnInit {
*/ */
siteClicked(siteId: string): void { siteClicked(siteId: string): void {
if (this.isRootURL) { if (this.isRootURL) {
CoreNavHelper.instance.openInSiteMainMenu('', {}, siteId); CoreNavigator.instance.navigateToSiteHome({ siteId });
} else if (this.action) { } else if (this.action) {
this.action.action(siteId); this.action.action(siteId);
} }

View File

@ -19,8 +19,7 @@ import { CoreDomUtils } from '@services/utils/dom';
import { CoreContentLinksDelegate, CoreContentLinksAction } from './contentlinks-delegate'; import { CoreContentLinksDelegate, CoreContentLinksAction } from './contentlinks-delegate';
import { CoreSite } from '@classes/site'; import { CoreSite } from '@classes/site';
import { makeSingleton, Translate } from '@singletons'; import { makeSingleton, Translate } from '@singletons';
import { Params } from '@angular/router'; import { CoreNavigator } from '@services/navigator';
import { CoreNavHelper } from '@services/nav-helper';
/** /**
* Service that provides some features regarding content links. * Service that provides some features regarding content links.
@ -92,10 +91,11 @@ export class CoreContentLinksHelperProvider {
* @param siteId Site ID. If not defined, current site. * @param siteId Site ID. If not defined, current site.
* @param checkMenu If true, check if the root page of a main menu tab. Only the page name will be checked. * @param checkMenu If true, check if the root page of a main menu tab. Only the page name will be checked.
* @return Promise resolved when done. * @return Promise resolved when done.
* @deprecated since 3.9.5. Use CoreNavHelperService.goInSite instead. * @deprecated since 3.9.5. Use CoreNavigator.navigateToSitePath instead.
*/ */
goInSite(pageName: string, pageParams: Params, siteId?: string, checkMenu?: boolean): Promise<void> { // eslint-disable-next-line @typescript-eslint/no-explicit-any
return CoreNavHelper.instance.goInSite(pageName, pageParams, siteId, checkMenu); async goInSite(navCtrl: NavController, pageName: string, pageParams: any, siteId?: string): Promise<void> {
await CoreNavigator.instance.navigateToSitePath(pageName, { params: pageParams, siteId });
} }
/** /**
@ -192,7 +192,7 @@ export class CoreContentLinksHelperProvider {
} }
} else { } else {
// Login in the site. // Login in the site.
return CoreNavHelper.instance.openInSiteMainMenu('', {}, site.getId()); await CoreNavigator.instance.navigateToSiteHome({ siteId: site.getId() });
} }
} }

View File

@ -34,7 +34,6 @@ import { CoreEnrolledCourseDataWithExtraInfoAndOptions } from '@features/courses
import { CoreArray } from '@singletons/array'; import { CoreArray } from '@singletons/array';
import { CoreIonLoadingElement } from '@classes/ion-loading'; import { CoreIonLoadingElement } from '@classes/ion-loading';
import { CoreCourseOffline } from './course-offline'; import { CoreCourseOffline } from './course-offline';
import { CoreNavHelper, CoreNavHelperService } from '@services/nav-helper';
import { import {
CoreCourseOptionsDelegate, CoreCourseOptionsDelegate,
CoreCourseOptionsHandlerToDisplay, CoreCourseOptionsHandlerToDisplay,
@ -979,7 +978,7 @@ export class CoreCourseHelperProvider {
* @param siteId Site ID. If not defined, current site. * @param siteId Site ID. If not defined, current site.
* @return Promise resolved when done. * @return Promise resolved when done.
*/ */
openCourse(course: CoreEnrolledCourseBasicData | { id: number }, params?: Params, siteId?: string): Promise<void> { async openCourse(course: CoreEnrolledCourseBasicData | { id: number }, params?: Params, siteId?: string): Promise<void> {
if (!siteId || siteId == CoreSites.instance.getCurrentSiteId()) { if (!siteId || siteId == CoreSites.instance.getCurrentSiteId()) {
// Current site, we can open the course. // Current site, we can open the course.
return CoreCourse.instance.openCourse(course, params); return CoreCourse.instance.openCourse(course, params);
@ -988,7 +987,9 @@ export class CoreCourseHelperProvider {
params = params || {}; params = params || {};
Object.assign(params, { course: course }); Object.assign(params, { course: course });
return CoreNavHelper.instance.openInSiteMainMenu(CoreNavHelperService.OPEN_COURSE, params, siteId); // @todo implement open course.
// await CoreNavigator.instance.navigateToSitePath('/course/.../...', { siteId, queryParams: params });
// return CoreNavigator.instance.openInSiteMainMenu(CoreNavigatorService.OPEN_COURSE, params, siteId);
} }
} }

View File

@ -13,7 +13,7 @@
// limitations under the License. // limitations under the License.
import { Component, OnDestroy, OnInit, QueryList, ViewChildren } from '@angular/core'; import { Component, OnDestroy, OnInit, QueryList, ViewChildren } from '@angular/core';
import { IonRefresher, NavController } from '@ionic/angular'; import { IonRefresher } from '@ionic/angular';
import { CoreCourses, CoreCoursesProvider } from '../../services/courses'; import { CoreCourses, CoreCoursesProvider } from '../../services/courses';
import { CoreEventObserver, CoreEvents } from '@singletons/events'; import { CoreEventObserver, CoreEvents } from '@singletons/events';
@ -22,6 +22,7 @@ import { CoreCoursesDashboard } from '@features/courses/services/dashboard';
import { CoreDomUtils } from '@services/utils/dom'; import { CoreDomUtils } from '@services/utils/dom';
import { CoreCourseBlock } from '@features/course/services/course'; import { CoreCourseBlock } from '@features/course/services/course';
import { CoreBlockComponent } from '@features/block/components/block/block'; import { CoreBlockComponent } from '@features/block/components/block/block';
import { CoreNavigator } from '@services/navigator';
/** /**
* Page that displays the dashboard page. * Page that displays the dashboard page.
@ -35,7 +36,6 @@ export class CoreCoursesDashboardPage implements OnInit, OnDestroy {
@ViewChildren(CoreBlockComponent) blocksComponents?: QueryList<CoreBlockComponent>; @ViewChildren(CoreBlockComponent) blocksComponents?: QueryList<CoreBlockComponent>;
searchEnabled = false; searchEnabled = false;
downloadEnabled = false; downloadEnabled = false;
downloadCourseEnabled = false; downloadCourseEnabled = false;
@ -47,10 +47,6 @@ export class CoreCoursesDashboardPage implements OnInit, OnDestroy {
protected updateSiteObserver?: CoreEventObserver; protected updateSiteObserver?: CoreEventObserver;
constructor(
protected navCtrl: NavController,
) { }
/** /**
* Initialize the component. * Initialize the component.
*/ */
@ -171,8 +167,8 @@ export class CoreCoursesDashboardPage implements OnInit, OnDestroy {
/** /**
* Go to search courses. * Go to search courses.
*/ */
openSearch(): void { async openSearch(): Promise<void> {
this.navCtrl.navigateForward(['/main/home/courses/search']); CoreNavigator.instance.navigateToSitePath('/courses/search');
} }
/** /**

View File

@ -18,7 +18,7 @@ import { CoreSites } from '@services/sites';
import { CoreDomUtils } from '@services/utils/dom'; import { CoreDomUtils } from '@services/utils/dom';
import { CoreLoginHelper } from '@features/login/services/login-helper'; import { CoreLoginHelper } from '@features/login/services/login-helper';
import { Translate } from '@singletons'; import { Translate } from '@singletons';
import { CoreNavHelper } from '@services/nav-helper'; import { CoreNavigator } from '@services/navigator';
/** /**
* Page that shows instructions to change the password. * Page that shows instructions to change the password.
@ -63,7 +63,7 @@ export class CoreLoginChangePasswordPage {
* Login the user. * Login the user.
*/ */
login(): void { login(): void {
CoreNavHelper.instance.goToSiteInitialPage(); CoreNavigator.instance.navigateToSiteHome();
this.changingPassword = false; this.changingPassword = false;
} }

View File

@ -26,7 +26,7 @@ import { CoreConstants } from '@/core/constants';
import { Translate } from '@singletons'; import { Translate } from '@singletons';
import { CoreSiteIdentityProvider, CoreSitePublicConfigResponse } from '@classes/site'; import { CoreSiteIdentityProvider, CoreSitePublicConfigResponse } from '@classes/site';
import { CoreEvents } from '@singletons/events'; import { CoreEvents } from '@singletons/events';
import { CoreNavHelper } from '@services/nav-helper'; import { CoreNavigator } from '@services/navigator';
/** /**
* Page to enter the user credentials. * Page to enter the user credentials.
@ -245,7 +245,8 @@ export class CoreLoginCredentialsPage implements OnInit, OnDestroy {
this.siteId = id; this.siteId = id;
await CoreNavHelper.instance.goToSiteInitialPage({ urlToOpen: this.urlToOpen }); // @todo test that this is working properly.
await CoreNavigator.instance.navigateToSiteHome({ params: { urlToOpen: this.urlToOpen } });
} catch (error) { } catch (error) {
CoreLoginHelper.instance.treatUserTokenError(siteUrl, error, username, password); CoreLoginHelper.instance.treatUserTokenError(siteUrl, error, username, password);

View File

@ -20,7 +20,7 @@ import { ApplicationInit, SplashScreen } from '@singletons';
import { CoreConstants } from '@/core/constants'; import { CoreConstants } from '@/core/constants';
import { CoreSites } from '@services/sites'; import { CoreSites } from '@services/sites';
import { CoreLoginHelper } from '@features/login/services/login-helper'; import { CoreLoginHelper } from '@features/login/services/login-helper';
import { CoreNavHelper } from '@services/nav-helper'; import { CoreNavigator } from '@services/navigator';
/** /**
* Page that displays a "splash screen" while the app is being initialized. * Page that displays a "splash screen" while the app is being initialized.
@ -32,6 +32,8 @@ import { CoreNavHelper } from '@services/nav-helper';
}) })
export class CoreLoginInitPage implements OnInit { export class CoreLoginInitPage implements OnInit {
// @todo this page should be removed in favor of native splash
// or a splash component rendered in the root app component
constructor(protected navCtrl: NavController) {} constructor(protected navCtrl: NavController) {}
/** /**
@ -80,17 +82,21 @@ export class CoreLoginInitPage implements OnInit {
return; return;
} }
return CoreNavHelper.instance.goToSiteInitialPage({ await CoreNavigator.instance.navigateToSiteHome({
redirectPage: redirectData.page, params: {
redirectParams: redirectData.params, redirectPath: redirectData.page,
redirectParams: redirectData.params,
},
}); });
return;
} catch (error) { } catch (error) {
// Site doesn't exist. // Site doesn't exist.
return this.loadPage(); return this.loadPage();
} }
} else if (redirectData.page) { } else if (redirectData.page) {
// No site to load, open the page. // No site to load, open the page.
return CoreNavHelper.instance.goToNoSitePage(redirectData.page, redirectData.params); // @todo return CoreNavigator.instance.goToNoSitePage(redirectData.page, redirectData.params);
} }
} }
@ -110,7 +116,9 @@ export class CoreLoginInitPage implements OnInit {
return this.loadPage(); return this.loadPage();
} }
return CoreNavHelper.instance.goToSiteInitialPage(); await CoreNavigator.instance.navigateToSiteHome();
return;
} }
await this.navCtrl.navigateRoot('/login/sites'); await this.navCtrl.navigateRoot('/login/sites');

View File

@ -25,7 +25,7 @@ import { CoreLoginHelper } from '@features/login/services/login-helper';
import { CoreSiteIdentityProvider, CoreSitePublicConfigResponse } from '@classes/site'; import { CoreSiteIdentityProvider, CoreSitePublicConfigResponse } from '@classes/site';
import { CoreEvents } from '@singletons/events'; import { CoreEvents } from '@singletons/events';
import { CoreError } from '@classes/errors/error'; import { CoreError } from '@classes/errors/error';
import { CoreNavHelper } from '@services/nav-helper'; import { CoreNavigator } from '@services/navigator';
/** /**
* Page to enter the user password to reconnect to a site. * Page to enter the user password to reconnect to a site.
@ -207,9 +207,12 @@ export class CoreLoginReconnectPage implements OnInit, OnDestroy {
this.credForm.controls['password'].reset(); this.credForm.controls['password'].reset();
// Go to the site initial page. // Go to the site initial page.
await CoreNavHelper.instance.goToSiteInitialPage({ // @todo test that this is working properly (could we use navigateToSitePath instead?).
redirectPage: this.page, await CoreNavigator.instance.navigateToSiteHome({
redirectParams: this.pageParams, params: {
redirectPath: this.page,
redirectParams: this.pageParams,
},
}); });
} catch (error) { } catch (error) {
CoreLoginHelper.instance.treatUserTokenError(this.siteUrl, error, this.username, password); CoreLoginHelper.instance.treatUserTokenError(this.siteUrl, error, this.username, password);

View File

@ -22,7 +22,7 @@ import { CoreUtils } from '@services/utils/utils';
import { CoreMimetypeUtils } from '@services/utils/mimetype'; import { CoreMimetypeUtils } from '@services/utils/mimetype';
import { CoreLoginHelper } from '@features/login/services/login-helper'; import { CoreLoginHelper } from '@features/login/services/login-helper';
import { CoreSite } from '@classes/site'; import { CoreSite } from '@classes/site';
import { CoreNavHelper } from '@services/nav-helper'; import { CoreNavigator } from '@services/navigator';
/** /**
* Page to accept a site policy. * Page to accept a site policy.
@ -129,7 +129,7 @@ export class CoreLoginSitePolicyPage implements OnInit {
// Invalidate cache since some WS don't return error if site policy is not accepted. // Invalidate cache since some WS don't return error if site policy is not accepted.
await CoreUtils.instance.ignoreErrors(this.currentSite!.invalidateWsCache()); await CoreUtils.instance.ignoreErrors(this.currentSite!.invalidateWsCache());
await CoreNavHelper.instance.goToSiteInitialPage(); await CoreNavigator.instance.navigateToSiteHome();
} catch (error) { } catch (error) {
CoreDomUtils.instance.showErrorModalDefault(error, 'Error accepting site policy.'); CoreDomUtils.instance.showErrorModalDefault(error, 'Error accepting site policy.');
} finally { } finally {

View File

@ -31,7 +31,7 @@ import { CoreUrl } from '@singletons/url';
import { CoreUrlUtils } from '@services/utils/url'; import { CoreUrlUtils } from '@services/utils/url';
import { CoreLoginSiteHelpComponent } from '@features/login/components/site-help/site-help'; import { CoreLoginSiteHelpComponent } from '@features/login/components/site-help/site-help';
import { CoreLoginSiteOnboardingComponent } from '@features/login/components/site-onboarding/site-onboarding'; import { CoreLoginSiteOnboardingComponent } from '@features/login/components/site-onboarding/site-onboarding';
import { CoreNavHelper } from '@services/nav-helper'; import { CoreNavigator } from '@services/navigator';
/** /**
* Page that displays a "splash screen" while the app is being initialized. * Page that displays a "splash screen" while the app is being initialized.
@ -329,7 +329,9 @@ export class CoreLoginSitePage implements OnInit {
CoreDomUtils.instance.triggerFormSubmittedEvent(this.formElement, true); CoreDomUtils.instance.triggerFormSubmittedEvent(this.formElement, true);
return CoreNavHelper.instance.goToSiteInitialPage(); await CoreNavigator.instance.navigateToSiteHome();
return;
} catch (error) { } catch (error) {
CoreLoginHelper.instance.treatUserTokenError(siteData.url, error, siteData.username, siteData.password); CoreLoginHelper.instance.treatUserTokenError(siteData.url, error, siteData.username, siteData.password);

View File

@ -19,7 +19,7 @@ import { Component, OnInit } from '@angular/core';
import { CoreSiteBasicInfo, CoreSites } from '@services/sites'; import { CoreSiteBasicInfo, CoreSites } from '@services/sites';
import { CoreLogger } from '@singletons/logger'; import { CoreLogger } from '@singletons/logger';
import { CoreLoginHelper } from '@features/login/services/login-helper'; import { CoreLoginHelper } from '@features/login/services/login-helper';
import { CoreNavHelper } from '@services/nav-helper'; import { CoreNavigator } from '@services/navigator';
/** /**
* Page that displays a "splash screen" while the app is being initialized. * Page that displays a "splash screen" while the app is being initialized.
@ -125,7 +125,9 @@ export class CoreLoginSitesPage implements OnInit {
const loggedIn = await CoreSites.instance.loadSite(siteId); const loggedIn = await CoreSites.instance.loadSite(siteId);
if (loggedIn) { if (loggedIn) {
return CoreNavHelper.instance.goToSiteInitialPage(); await CoreNavigator.instance.navigateToSiteHome();
return;
} }
} catch (error) { } catch (error) {
this.logger.error('Error loading site ' + siteId, error); this.logger.error('Error loading site ' + siteId, error);

View File

@ -34,7 +34,7 @@ import { makeSingleton, Translate } from '@singletons';
import { CoreLogger } from '@singletons/logger'; import { CoreLogger } from '@singletons/logger';
import { CoreUrl } from '@singletons/url'; import { CoreUrl } from '@singletons/url';
import { CoreObject } from '@singletons/object'; import { CoreObject } from '@singletons/object';
import { CoreNavHelper, CoreNavHelperOpenMainMenuOptions, CoreNavHelperService } from '@services/nav-helper'; import { CoreNavigator } from '@services/navigator';
/** /**
* Helper provider that provides some common features regarding authentication. * Helper provider that provides some common features regarding authentication.
@ -42,7 +42,11 @@ import { CoreNavHelper, CoreNavHelperOpenMainMenuOptions, CoreNavHelperService }
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class CoreLoginHelperProvider { export class CoreLoginHelperProvider {
static readonly OPEN_COURSE = CoreNavHelperService.OPEN_COURSE; // @deprecated since 3.9.5. /**
* @deprecated since 3.9.5.
*/
static readonly OPEN_COURSE = 'open_course';
static readonly ONBOARDING_DONE = 'onboarding_done'; static readonly ONBOARDING_DONE = 'onboarding_done';
static readonly FAQ_URL_IMAGE_HTML = '<img src="assets/img/login/faq_url.png" role="presentation">'; static readonly FAQ_URL_IMAGE_HTML = '<img src="assets/img/login/faq_url.png" role="presentation">';
static readonly FAQ_QRCODE_IMAGE_HTML = '<img src="assets/img/login/faq_qrcode.png" role="presentation">'; static readonly FAQ_QRCODE_IMAGE_HTML = '<img src="assets/img/login/faq_qrcode.png" role="presentation">';
@ -111,9 +115,12 @@ export class CoreLoginHelperProvider {
*/ */
checkLogout(): void { checkLogout(): void {
const currentSite = CoreSites.instance.getCurrentSite(); const currentSite = CoreSites.instance.getCurrentSite();
const currentPage = CoreNavHelper.instance.getCurrentPage();
if (!CoreApp.instance.isSSOAuthenticationOngoing() && currentSite?.isLoggedOut() && currentPage == '/login/reconnect') { if (
!CoreApp.instance.isSSOAuthenticationOngoing() &&
currentSite?.isLoggedOut() &&
CoreNavigator.instance.isCurrent('/login/reconnect')
) {
// User must reauthenticate but he closed the InAppBrowser without doing so, logout him. // User must reauthenticate but he closed the InAppBrowser without doing so, logout him.
CoreSites.instance.logout(); CoreSites.instance.logout();
} }
@ -436,21 +443,33 @@ export class CoreLoginHelperProvider {
* @param page Page to open. * @param page Page to open.
* @param params Params of the page. * @param params Params of the page.
* @return Promise resolved when done. * @return Promise resolved when done.
* @deprecated since 3.9.5. Use CoreNavHelperService.goToNoSitePage instead. * @deprecated since 3.9.5. Use CoreNavigator.navigateToLoginCredentials instead.
*/ */
goToNoSitePage(page: string, params?: Params): Promise<void> { async goToNoSitePage(page: string, params?: Params): Promise<void> {
return CoreNavHelper.instance.goToNoSitePage(page, params); await CoreNavigator.instance.navigateToLoginCredentials(params);
} }
/** /**
* Go to the initial page of a site depending on 'userhomepage' setting. * Go to the initial page of a site depending on 'userhomepage' setting.
* *
* @param options Options. * @param navCtrl NavController to use. Defaults to app root NavController.
* @param page Name of the page to load after loading the main page.
* @param params Params to pass to the page.
* @param options Navigation options.
* @param url URL to open once the main menu is loaded.
* @return Promise resolved when done. * @return Promise resolved when done.
* @deprecated since 3.9.5. Use CoreNavHelperService.goToSiteInitialPage instead. * @deprecated since 3.9.5. Use CoreNavigator.navigateToSiteHome or CoreNavigator.navigateToSitePath instead.
*/ */
goToSiteInitialPage(options?: CoreNavHelperOpenMainMenuOptions): Promise<void> { // eslint-disable-next-line @typescript-eslint/no-explicit-any
return CoreNavHelper.instance.goToSiteInitialPage(options); async goToSiteInitialPage(navCtrl?: NavController, page?: string, params?: any, options?: any, url?: string): Promise<void> {
await CoreNavigator.instance.navigateToSiteHome({
...options,
params: {
redirectPath: page,
redirectParams: params,
urlToOpen: url,
},
});
} }
/** /**
@ -606,10 +625,10 @@ export class CoreLoginHelperProvider {
* *
* @param page Name of the page to load. * @param page Name of the page to load.
* @param params Params to pass to the page. * @param params Params to pass to the page.
* @deprecated since 3.9.5. Use CoreNavHelperService.loadPageInMainMenu instead. * @deprecated since 3.9.5. Use CoreNavigator.navigateToSitepath instead.
*/ */
loadPageInMainMenu(page: string, params?: Params): void { loadPageInMainMenu(page: string, params?: Params): void {
CoreNavHelper.instance.loadPageInMainMenu(page, params); CoreNavigator.instance.navigateToSitePath(page, { params });
} }
/** /**
@ -767,10 +786,8 @@ export class CoreLoginHelperProvider {
return; // Site that triggered the event is not current site. return; // Site that triggered the event is not current site.
} }
const currentPage = CoreNavHelper.instance.getCurrentPage();
// If current page is already change password, stop. // If current page is already change password, stop.
if (currentPage == '/login/changepassword') { if (CoreNavigator.instance.isCurrent('/login/changepassword')) {
return; return;
} }
@ -826,14 +843,14 @@ export class CoreLoginHelperProvider {
/** /**
* Redirect to a new page, setting it as the root page and loading the right site if needed. * Redirect to a new page, setting it as the root page and loading the right site if needed.
* *
* @param page Name of the page to load. Special cases: CoreNavHelperService.OPEN_COURSE (to open course page). * @param page Name of the page to load.
* @param params Params to pass to the page. * @param params Params to pass to the page.
* @param siteId Site to load. If not defined, current site. * @param siteId Site to load. If not defined, current site.
* @return Promise resolved when done. * @return Promise resolved when done.
* @deprecated since 3.9.5. Use CoreNavHelperService.openInSiteMainMenu instead. * @deprecated since 3.9.5. Use CoreNavigator.navigateToSitePath instead.
*/ */
redirect(page: string, params?: Params, siteId?: string): Promise<void> { async redirect(page: string, params?: Params, siteId?: string): Promise<void> {
return CoreNavHelper.instance.openInSiteMainMenu(page, params, siteId); await CoreNavigator.instance.navigateToSitePath(page, { params, siteId });
} }
/** /**
@ -959,14 +976,14 @@ export class CoreLoginHelperProvider {
const info = currentSite.getInfo(); const info = currentSite.getInfo();
if (typeof info != 'undefined' && typeof info.username != 'undefined' && !this.isOpeningReconnect) { if (typeof info != 'undefined' && typeof info.username != 'undefined' && !this.isOpeningReconnect) {
// If current page is already reconnect, stop. // If current page is already reconnect, stop.
if (CoreNavHelper.instance.getCurrentPage() == '/login/reconnect') { if (CoreNavigator.instance.isCurrent('/login/reconnect')) {
return; return;
} }
this.isOpeningReconnect = true; this.isOpeningReconnect = true;
await CoreUtils.instance.ignoreErrors(this.navCtrl.navigateRoot('/login/reconnect', { await CoreUtils.instance.ignoreErrors(this.navCtrl.navigateRoot('/login/reconnect', {
queryParams: CoreObject.removeUndefined({ queryParams: CoreObject.withoutEmpty({
siteId, siteId,
pageName: data.pageName, pageName: data.pageName,
pageParams: data.params, pageParams: data.params,
@ -1127,7 +1144,7 @@ export class CoreLoginHelperProvider {
} }
// If current page is already site policy, stop. // If current page is already site policy, stop.
if (CoreNavHelper.instance.getCurrentPage() == '/login/sitepolicy') { if (CoreNavigator.instance.isCurrent('/login/sitepolicy')) {
return; return;
} }

View File

@ -2,9 +2,6 @@
<ion-tab-bar slot="bottom" [hidden]="hidden"> <ion-tab-bar slot="bottom" [hidden]="hidden">
<ion-spinner *ngIf="!loaded"></ion-spinner> <ion-spinner *ngIf="!loaded"></ion-spinner>
<ion-tab-button tab="redirect" [disabled]="true" [hidden]="true"></ion-tab-button>
<!-- @todo: [root]="redirectPage" [rootParams]="redirectParams" -->
<ion-tab-button (ionTabButtonClick)="tabClicked($event, tab.page)" [hidden]="!loaded && tab.hide" *ngFor="let tab of tabs" <ion-tab-button (ionTabButtonClick)="tabClicked($event, tab.page)" [hidden]="!loaded && tab.hide" *ngFor="let tab of tabs"
[tab]="tab.page" [disabled]="tab.hide" layout="label-hide" class="{{tab.class}}"> [tab]="tab.page" [disabled]="tab.hide" layout="label-hide" class="{{tab.class}}">
<ion-icon [name]="tab.icon"></ion-icon> <ion-icon [name]="tab.icon"></ion-icon>

View File

@ -13,19 +13,19 @@
// limitations under the License. // limitations under the License.
import { Component, OnInit, OnDestroy, ViewChild, ChangeDetectorRef } from '@angular/core'; import { Component, OnInit, OnDestroy, ViewChild, ChangeDetectorRef } from '@angular/core';
import { ActivatedRoute, Params, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { NavController, IonTabs } from '@ionic/angular'; import { NavController, IonTabs } from '@ionic/angular';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { CoreApp } from '@services/app'; import { CoreApp } from '@services/app';
import { CoreSites } from '@services/sites'; import { CoreSites } from '@services/sites';
import { CoreTextUtils } from '@services/utils/text'; import { CoreTextUtils } from '@services/utils/text';
import { CoreEvents, CoreEventObserver, CoreEventLoadPageMainMenuData } from '@singletons/events'; import { CoreEvents, CoreEventObserver } from '@singletons/events';
import { CoreMainMenu } from '../../services/mainmenu'; import { CoreMainMenu } from '../../services/mainmenu';
import { CoreMainMenuDelegate, CoreMainMenuHandlerToDisplay } from '../../services/mainmenu-delegate'; import { CoreMainMenuDelegate, CoreMainMenuHandlerToDisplay } from '../../services/mainmenu-delegate';
import { CoreDomUtils } from '@services/utils/dom'; import { CoreDomUtils } from '@services/utils/dom';
import { Translate } from '@singletons'; import { Translate } from '@singletons';
import { CoreNavHelper } from '@services/nav-helper'; import { CoreRedirectPayload } from '@services/navigator';
/** /**
* Page that displays the main menu of the app. * Page that displays the main menu of the app.
@ -40,17 +40,14 @@ export class CoreMainMenuPage implements OnInit, OnDestroy {
tabs: CoreMainMenuHandlerToDisplay[] = []; tabs: CoreMainMenuHandlerToDisplay[] = [];
allHandlers?: CoreMainMenuHandlerToDisplay[]; allHandlers?: CoreMainMenuHandlerToDisplay[];
loaded = false; loaded = false;
redirectPage?: string;
redirectParams?: Params;
showTabs = false; showTabs = false;
tabsPlacement = 'bottom'; tabsPlacement = 'bottom';
hidden = false; hidden = false;
protected subscription?: Subscription; protected subscription?: Subscription;
protected redirectObs?: CoreEventObserver; protected redirectObs?: CoreEventObserver;
protected pendingRedirect?: CoreEventLoadPageMainMenuData; protected pendingRedirect?: CoreRedirectPayload;
protected urlToOpen?: string; protected urlToOpen?: string;
protected mainMenuId: number;
protected keyboardObserver?: CoreEventObserver; protected keyboardObserver?: CoreEventObserver;
@ViewChild('mainTabs') mainTabs?: IonTabs; @ViewChild('mainTabs') mainTabs?: IonTabs;
@ -60,44 +57,32 @@ export class CoreMainMenuPage implements OnInit, OnDestroy {
protected navCtrl: NavController, protected navCtrl: NavController,
protected changeDetector: ChangeDetectorRef, protected changeDetector: ChangeDetectorRef,
protected router: Router, protected router: Router,
) { ) {}
this.mainMenuId = CoreNavHelper.instance.getMainMenuId();
}
/** /**
* Initialize the component. * Initialize the component.
*/ */
ngOnInit(): void { ngOnInit(): void {
// @TODO this should be handled by route guards and can be removed
if (!CoreSites.instance.isLoggedIn()) { if (!CoreSites.instance.isLoggedIn()) {
this.navCtrl.navigateRoot('/login/init'); this.navCtrl.navigateRoot('/login/init');
return; return;
} }
this.route.queryParams.subscribe(params => { this.route.queryParams.subscribe((params: Partial<CoreRedirectPayload> & { urlToOpen?: string }) => {
const redirectPage = params['redirectPage']; if (params.redirectPath) {
if (redirectPage) {
this.pendingRedirect = { this.pendingRedirect = {
redirectPage: redirectPage, redirectPath: params.redirectPath,
redirectParams: params['redirectParams'], redirectParams: params.redirectParams,
}; };
} }
this.urlToOpen = params['urlToOpen']; this.urlToOpen = params.urlToOpen;
}); });
this.showTabs = true; this.showTabs = true;
this.redirectObs = CoreEvents.on(CoreEvents.LOAD_PAGE_MAIN_MENU, (data: CoreEventLoadPageMainMenuData) => {
if (!this.loaded) {
// View isn't ready yet, wait for it to be ready.
this.pendingRedirect = data;
} else {
delete this.pendingRedirect;
this.handleRedirect(data);
}
});
this.subscription = CoreMainMenuDelegate.instance.getHandlersObservable().subscribe((handlers) => { this.subscription = CoreMainMenuDelegate.instance.getHandlersObservable().subscribe((handlers) => {
// Remove the handlers that should only appear in the More menu. // Remove the handlers that should only appear in the More menu.
this.allHandlers = handlers.filter((handler) => !handler.onlyInMore); this.allHandlers = handlers.filter((handler) => !handler.onlyInMore);
@ -131,8 +116,6 @@ export class CoreMainMenuPage implements OnInit, OnDestroy {
} }
}); });
} }
CoreNavHelper.instance.setMainMenuOpen(this.mainMenuId, true);
} }
/** /**
@ -201,13 +184,13 @@ export class CoreMainMenuPage implements OnInit, OnDestroy {
* *
* @param data Data received. * @param data Data received.
*/ */
protected handleRedirect(data: CoreEventLoadPageMainMenuData): void { protected handleRedirect(data: CoreRedirectPayload): void {
// Check if the redirect page is the root page of any of the tabs. // Check if the redirect page is the root page of any of the tabs.
const i = this.tabs.findIndex((tab) => tab.page == data.redirectPage); const i = this.tabs.findIndex((tab) => tab.page == data.redirectPath);
if (i >= 0) { if (i >= 0) {
// Tab found. Open it with the params. // Tab found. Open it with the params.
this.navCtrl.navigateForward(data.redirectPage, { this.navCtrl.navigateForward(data.redirectPath, {
queryParams: data.redirectParams, queryParams: data.redirectParams,
animated: false, animated: false,
}); });
@ -227,7 +210,6 @@ export class CoreMainMenuPage implements OnInit, OnDestroy {
this.subscription?.unsubscribe(); this.subscription?.unsubscribe();
this.redirectObs?.off(); this.redirectObs?.off();
window.removeEventListener('resize', this.initHandlers.bind(this)); window.removeEventListener('resize', this.initHandlers.bind(this));
CoreNavHelper.instance.setMainMenuOpen(this.mainMenuId, false);
this.keyboardObserver?.off(); this.keyboardObserver?.off();
} }

View File

@ -17,7 +17,6 @@ import { Injectable } from '@angular/core';
import { CoreApp } from '@services/app'; import { CoreApp } from '@services/app';
import { CoreLang } from '@services/lang'; import { CoreLang } from '@services/lang';
import { CoreSites } from '@services/sites'; import { CoreSites } from '@services/sites';
import { CoreUtils } from '@services/utils/utils';
import { CoreConstants } from '@/core/constants'; import { CoreConstants } from '@/core/constants';
import { CoreMainMenuDelegate, CoreMainMenuHandlerToDisplay } from './mainmenu-delegate'; import { CoreMainMenuDelegate, CoreMainMenuHandlerToDisplay } from './mainmenu-delegate';
import { makeSingleton } from '@singletons'; import { makeSingleton } from '@singletons';
@ -42,20 +41,10 @@ export class CoreMainMenuProvider {
* *
* @return Promise resolved with the current main menu handlers. * @return Promise resolved with the current main menu handlers.
*/ */
getCurrentMainMenuHandlers(): Promise<CoreMainMenuHandlerToDisplay[]> { getCurrentMainMenuHandlers(): CoreMainMenuHandlerToDisplay[] {
const deferred = CoreUtils.instance.promiseDefer<CoreMainMenuHandlerToDisplay[]>(); return CoreMainMenuDelegate.instance.getHandlers()
.filter(handler => !handler.onlyInMore)
const subscription = CoreMainMenuDelegate.instance.getHandlersObservable().subscribe((handlers) => { .slice(0, this.getNumItems());
subscription?.unsubscribe();
// Remove the handlers that should only appear in the More menu.
handlers = handlers.filter(handler => !handler.onlyInMore);
// Return main handlers.
deferred.resolve(handlers.slice(0, this.getNumItems()));
});
return deferred.promise;
} }
/** /**

View File

@ -19,7 +19,7 @@ import { CoreContentLinksHandlerBase } from '@features/contentlinks/classes/base
import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate'; import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate';
import { CoreSiteHome } from '../sitehome'; import { CoreSiteHome } from '../sitehome';
import { makeSingleton } from '@singletons'; import { makeSingleton } from '@singletons';
import { CoreNavHelper } from '@services/nav-helper'; import { CoreNavigator } from '@services/navigator';
/** /**
* Handler to treat links to site home index. * Handler to treat links to site home index.
@ -43,7 +43,8 @@ export class CoreSiteHomeIndexLinkHandlerService extends CoreContentLinksHandler
getActions(): CoreContentLinksAction[] | Promise<CoreContentLinksAction[]> { getActions(): CoreContentLinksAction[] | Promise<CoreContentLinksAction[]> {
return [{ return [{
action: (siteId: string): void => { action: (siteId: string): void => {
CoreNavHelper.instance.goInSite('sitehome', [], siteId); // @todo This should open the 'sitehome' setting as well.
CoreNavigator.instance.navigateToSiteHome({ siteId });
}, },
}]; }];
} }

View File

@ -16,7 +16,7 @@ import { Injectable } from '@angular/core';
import { Params } from '@angular/router'; import { Params } from '@angular/router';
import { CoreContentLinksHandlerBase } from '@features/contentlinks/classes/base-handler'; import { CoreContentLinksHandlerBase } from '@features/contentlinks/classes/base-handler';
import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate'; import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate';
import { CoreNavHelper } from '@services/nav-helper'; import { CoreNavigator } from '@services/navigator';
import { makeSingleton } from '@singletons'; import { makeSingleton } from '@singletons';
import { CoreTag } from '../tag'; import { CoreTag } from '../tag';
@ -57,11 +57,11 @@ export class CoreTagIndexLinkHandlerService extends CoreContentLinksHandlerBase
}; };
if (!pageParams.tagId && (!pageParams.tagName || !pageParams.collectionId)) { if (!pageParams.tagId && (!pageParams.tagName || !pageParams.collectionId)) {
CoreNavHelper.instance.goInSite('/tag/search', {}, siteId); CoreNavigator.instance.navigateToSitePath('/tag/search', { siteId });
} else if (pageParams.areaId) { } else if (pageParams.areaId) {
CoreNavHelper.instance.goInSite('/tag/index-area', pageParams, siteId); CoreNavigator.instance.navigateToSitePath('/tag/index-area', { params: pageParams, siteId });
} else { } else {
CoreNavHelper.instance.goInSite('/tag/index', pageParams, siteId); CoreNavigator.instance.navigateToSitePath('/tag/index', { params: pageParams, siteId });
} }
}, },
}]; }];

View File

@ -16,7 +16,7 @@ import { Injectable } from '@angular/core';
import { Params } from '@angular/router'; import { Params } from '@angular/router';
import { CoreContentLinksHandlerBase } from '@features/contentlinks/classes/base-handler'; import { CoreContentLinksHandlerBase } from '@features/contentlinks/classes/base-handler';
import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate'; import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate';
import { CoreNavHelper } from '@services/nav-helper'; import { CoreNavigator } from '@services/navigator';
import { makeSingleton } from '@singletons'; import { makeSingleton } from '@singletons';
import { CoreTag } from '../tag'; import { CoreTag } from '../tag';
@ -47,7 +47,7 @@ export class CoreTagSearchLinkHandlerService extends CoreContentLinksHandlerBase
query: params.query || '', query: params.query || '',
}; };
CoreNavHelper.instance.goInSite('/tag/search', pageParams, siteId); CoreNavigator.instance.navigateToSitePath('/tag/search', { params: pageParams, siteId });
}, },
}]; }];
} }

View File

@ -17,7 +17,7 @@ import { Params } from '@angular/router';
import { CoreContentLinksHandlerBase } from '@features/contentlinks/classes/base-handler'; import { CoreContentLinksHandlerBase } from '@features/contentlinks/classes/base-handler';
import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate'; import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate';
import { CoreNavHelper } from '@services/nav-helper'; import { CoreNavigator } from '@services/navigator';
import { makeSingleton } from '@singletons'; import { makeSingleton } from '@singletons';
/** /**
@ -54,7 +54,7 @@ export class CoreUserProfileLinkHandlerService extends CoreContentLinksHandlerBa
userId: parseInt(params.id, 10), userId: parseInt(params.id, 10),
}; };
CoreNavHelper.instance.goInSite('/user', pageParams, siteId); CoreNavigator.instance.navigateToSitePath('/user', { params: pageParams, siteId });
}, },
}]; }];
} }

View File

@ -25,7 +25,6 @@ import { makeSingleton, Keyboard, Network, StatusBar, Platform, Device } from '@
import { CoreLogger } from '@singletons/logger'; import { CoreLogger } from '@singletons/logger';
import { CoreColors } from '@singletons/colors'; import { CoreColors } from '@singletons/colors';
import { DBNAME, SCHEMA_VERSIONS_TABLE_NAME, SCHEMA_VERSIONS_TABLE_SCHEMA, SchemaVersionsDBEntry } from '@services/database/app'; import { DBNAME, SCHEMA_VERSIONS_TABLE_NAME, SCHEMA_VERSIONS_TABLE_SCHEMA, SchemaVersionsDBEntry } from '@services/database/app';
import { CoreNavHelper } from './nav-helper';
/** /**
* Object responsible of managing schema versions. * Object responsible of managing schema versions.
@ -178,10 +177,10 @@ export class CoreAppProvider {
* Get an ID for a main menu. * Get an ID for a main menu.
* *
* @return Main menu ID. * @return Main menu ID.
* @deprecated since 3.9.5. Use CoreNavHelperService.getMainMenuId instead. * @deprecated since 3.9.5. No longer supported.
*/ */
getMainMenuId(): number { getMainMenuId(): number {
return CoreNavHelper.instance.getMainMenuId(); return 0;
} }
/** /**
@ -306,10 +305,10 @@ export class CoreAppProvider {
* Check if the main menu is open. * Check if the main menu is open.
* *
* @return Whether the main menu is open. * @return Whether the main menu is open.
* @deprecated since 3.9.5. Use CoreNavHelperService.isMainMenuOpen instead. * @deprecated since 3.9.5. No longer supported.
*/ */
isMainMenuOpen(): boolean { isMainMenuOpen(): boolean {
return CoreNavHelper.instance.isMainMenuOpen(); return false;
} }
/** /**
@ -445,17 +444,6 @@ export class CoreAppProvider {
this.keyboardClosing = false; this.keyboardClosing = false;
} }
/**
* Set a main menu as open or not.
*
* @param id Main menu ID.
* @param open Whether it's open or not.
* @deprecated since 3.9.5. Use CoreNavHelperService.setMainMenuOpen instead.
*/
setMainMenuOpen(id: number, open: boolean): void {
CoreNavHelper.instance.setMainMenuOpen(id, open);
}
/** /**
* Start an SSO authentication process. * Start an SSO authentication process.
* Please notice that this function should be called when the app receives the new token from the browser, * Please notice that this function should be called when the app receives the new token from the browser,

View File

@ -1,364 +0,0 @@
// (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 { Injectable } from '@angular/core';
import { Params, Router } from '@angular/router';
import { CoreMainMenu } from '@features/mainmenu/services/mainmenu';
import { NavController } from '@ionic/angular';
import { NavigationOptions } from '@ionic/angular/providers/nav-controller';
import { makeSingleton } from '@singletons';
import { CoreEvents } from '@singletons/events';
import { CoreConstants } from '../constants';
import { CoreSites } from './sites';
import { CoreDomUtils } from './utils/dom';
import { CoreTextUtils } from './utils/text';
import { CoreUrlUtils } from './utils/url';
/**
* Provider to provide some helper functions regarding navigation.
*/
@Injectable({ providedIn: 'root' })
export class CoreNavHelperService {
static readonly OPEN_COURSE = 'open_course';
protected pageToLoad?: {page: string; params?: Params; time: number}; // Page to load once main menu is opened.
protected mainMenuOpen?: number;
protected mainMenuId = 0;
constructor(
protected router: Router,
protected navCtrl: NavController,
) {
CoreEvents.on(CoreEvents.MAIN_MENU_OPEN, () => {
/* If there is any page pending to be opened, do it now. Don't open pages stored more than 5 seconds ago, probably
the function to open the page was called when it shouldn't. */
if (this.pageToLoad && Date.now() - this.pageToLoad.time < 5000) {
this.loadPageInMainMenu(this.pageToLoad.page, this.pageToLoad.params);
delete this.pageToLoad;
}
});
}
/**
* Get current page route without params.
*
* @return Current page route.
*/
getCurrentPage(): string {
return CoreUrlUtils.instance.removeUrlParams(this.router.url);
}
/**
* Open a new page in the current main menu tab.
*
* @param page Page to open.
* @param pageParams Params to send to the page.
* @return Promise resolved when done.
*/
async goInCurrentMainMenuTab(page: string, pageParams: Params): Promise<void> {
const currentPage = this.getCurrentPage();
const routeMatch = currentPage.match(/^\/main\/([^/]+)/);
if (!routeMatch || !routeMatch[0]) {
// Not in a tab. Stop.
return;
}
let path = '';
if (routeMatch[1] && page.match(new RegExp(`^/${routeMatch[1]}(/|$)`))) {
path = CoreTextUtils.instance.concatenatePaths('/main', page);
} else {
path = CoreTextUtils.instance.concatenatePaths(routeMatch[0], page);
}
await this.navCtrl.navigateForward(path, {
queryParams: pageParams,
});
}
/**
* Goes to a certain page in a certain site. If the site is current site it will perform a regular navigation,
* otherwise it will load the other site and open the page in main menu.
*
* @param path Path to go.
* @param pageParams Params to send to the page.
* @param siteId Site ID. If not defined, current site.
* @param checkMenu If true, check if the root page is on a main menu tab. Only the path will be checked.
* @return Promise resolved when done.
*/
async goInSite(path: string, pageParams: Params, siteId?: string, checkMenu?: boolean): Promise<void> {
siteId = siteId || CoreSites.instance.getCurrentSiteId();
// @todo: When this function was in ContentLinksHelper, this code was inside NgZone. Check if it's needed.
if (!CoreSites.instance.isLoggedIn() || siteId != CoreSites.instance.getCurrentSiteId()) {
await this.openInSiteMainMenu(path, pageParams, siteId);
return;
}
if (checkMenu) {
let isInMenu = false;
// Check if the page is in the main menu.
try {
isInMenu = await CoreMainMenu.instance.isCurrentMainMenuHandler(path);
} catch {
isInMenu = false;
}
if (isInMenu) {
// Just select the tab. @todo test.
CoreNavHelper.instance.loadPageInMainMenu(path, pageParams);
return;
}
}
await this.goInCurrentMainMenuTab(path, pageParams);
}
/**
* Get an ID for a main menu.
*
* @return Main menu ID.
*/
getMainMenuId(): number {
return this.mainMenuId++;
}
/**
* Open a page that doesn't belong to any site.
*
* @param page Page to open.
* @param params Params of the page.
* @return Promise resolved when done.
*/
async goToNoSitePage(page: string, params?: Params): Promise<void> {
const currentPage = this.getCurrentPage();
if (currentPage == page) {
// Already at page, nothing to do.
return;
}
if (page == '/login/sites') {
// Just open the page as root.
await this.navCtrl.navigateRoot(page, { queryParams: params });
return;
}
if (page == '/login/credentials' && currentPage == '/login/site') {
// Just open the new page to keep the navigation history.
await this.navCtrl.navigateForward(page, { queryParams: params });
return;
}
// Check if there is any site stored.
const hasSites = await CoreSites.instance.hasSites();
if (!hasSites) {
// There are sites stored, open sites page first to be able to go back.
await this.navCtrl.navigateRoot('/login/sites');
await this.navCtrl.navigateForward(page, { queryParams: params });
return;
}
if (page != '/login/site') {
// Open the new site page to be able to go back.
await this.navCtrl.navigateRoot('/login/site');
await this.navCtrl.navigateForward(page, { queryParams: params });
} else {
// Just open the page as root.
await this.navCtrl.navigateRoot(page, { queryParams: params });
}
}
/**
* Go to the initial page of a site depending on 'userhomepage' setting.
*
* @param options Options.
* @return Promise resolved when done.
*/
goToSiteInitialPage(options?: CoreNavHelperOpenMainMenuOptions): Promise<void> {
return this.openMainMenu(options);
}
/**
* Check if the main menu is open.
*
* @return Whether the main menu is open.
*/
isMainMenuOpen(): boolean {
return typeof this.mainMenuOpen != 'undefined';
}
/**
* Load a certain page in the main menu.
*
* @param page Route of the page to load.
* @param params Params to pass to the page.
*/
loadPageInMainMenu(page: string, params?: Params): void {
if (!this.isMainMenuOpen()) {
// Main menu not open. Store the page to be loaded later.
this.pageToLoad = {
page: page,
params: params,
time: Date.now(),
};
return;
}
if (page == CoreNavHelperService.OPEN_COURSE) {
// @todo Use the openCourse function.
} else {
CoreEvents.trigger(CoreEvents.LOAD_PAGE_MAIN_MENU, { redirectPage: page, redirectParams: params });
}
}
/**
* Load a site and load a certain page in that site.
*
* @param siteId Site to load.
* @param page Name of the page to load.
* @param params Params to pass to the page.
* @return Promise resolved when done.
*/
protected async loadSiteAndPage(siteId: string, page: string, params?: Params): Promise<void> {
if (siteId == CoreConstants.NO_SITE_ID) {
// Page doesn't belong to a site, just load the page.
await this.navCtrl.navigateRoot(page, params);
return;
}
const modal = await CoreDomUtils.instance.showModalLoading();
try {
const loggedIn = await CoreSites.instance.loadSite(siteId, page, params);
if (!loggedIn) {
return;
}
await this.openMainMenu({
redirectPage: page,
redirectParams: params,
});
} catch (error) {
// Site doesn't exist.
await this.navCtrl.navigateRoot('/login/sites');
} finally {
modal.dismiss();
}
}
/**
* Open the main menu, loading a certain page.
*
* @param options Options.
* @return Promise resolved when done.
*/
protected async openMainMenu(options?: CoreNavHelperOpenMainMenuOptions): Promise<void> {
// Due to DeepLinker, we need to remove the path from the URL before going to main menu.
// IonTabs checks the URL to determine which path to load for deep linking, so we clear the URL.
// @todo this.location.replaceState('');
if (options?.redirectPage == CoreNavHelperService.OPEN_COURSE) {
// Load the main menu first, and then open the course.
try {
await this.navCtrl.navigateRoot('/');
} finally {
// @todo: Open course.
}
} else {
// Open the main menu.
const queryParams: Params = Object.assign({}, options);
delete queryParams.navigationOptions;
await this.navCtrl.navigateRoot('/', {
queryParams,
...options?.navigationOptions,
});
}
}
/**
* Open a new page, setting it as the root page and loading the right site if needed.
*
* @param page Name of the page to load. Special cases: OPEN_COURSE (to open course page).
* @param params Params to pass to the page.
* @param siteId Site to load. If not defined, current site.
* @return Promise resolved when done.
*/
async openInSiteMainMenu(page: string, params?: Params, siteId?: string): Promise<void> {
siteId = siteId || CoreSites.instance.getCurrentSiteId();
if (!CoreSites.instance.isLoggedIn()) {
if (siteId) {
await this.loadSiteAndPage(siteId, page, params);
} else {
await this.navCtrl.navigateRoot('/login/sites');
}
return;
}
if (siteId && siteId != CoreSites.instance.getCurrentSiteId()) {
// Target page belongs to a different site. Change site.
// @todo: Check site plugins.
await CoreSites.instance.logout();
await this.loadSiteAndPage(siteId, page, params);
} else {
// Current page, open it in main menu.
this.loadPageInMainMenu(page, params);
}
}
/**
* Set a main menu as open or not.
*
* @param id Main menu ID.
* @param open Whether it's open or not.
*/
setMainMenuOpen(id: number, open: boolean): void {
if (open) {
this.mainMenuOpen = id;
CoreEvents.trigger(CoreEvents.MAIN_MENU_OPEN);
} else if (this.mainMenuOpen == id) {
delete this.mainMenuOpen;
}
}
}
export class CoreNavHelper extends makeSingleton(CoreNavHelperService) {}
export type CoreNavHelperOpenMainMenuOptions = {
redirectPage?: string; // Route of the page to open in main menu. If not defined, default tab will be selected.
redirectParams?: Params; // Params to pass to the selected tab if any.
urlToOpen?: string; // URL to open once the main menu is loaded.
navigationOptions?: NavigationOptions; // Navigation options.
};

View File

@ -0,0 +1,247 @@
// (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 { Injectable } from '@angular/core';
import { ActivatedRoute, Params } from '@angular/router';
import { NavigationOptions } from '@ionic/angular/providers/nav-controller';
import { CoreConstants } from '@/core/constants';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreMainMenu } from '@features/mainmenu/services/mainmenu';
import { CoreMainMenuHomeHandlerService } from '@features/mainmenu/services/handlers/mainmenu';
import { CoreObject } from '@singletons/object';
import { CoreSites } from '@services/sites';
import { CoreUtils } from '@services/utils/utils';
import { CoreUrlUtils } from '@services/utils/url';
import { makeSingleton, NavController, Router } from '@singletons';
const DEFAULT_MAIN_MENU_TAB = CoreMainMenuHomeHandlerService.PAGE_NAME;
/**
* Redirect payload.
*/
export type CoreRedirectPayload = {
redirectPath: string;
redirectParams?: Params;
};
/**
* Navigation options.
*/
export type CoreNavigationOptions = {
animated?: boolean;
params?: Params;
reset?: boolean;
};
/**
* Service to provide some helper functions regarding navigation.
*/
@Injectable({ providedIn: 'root' })
export class CoreNavigatorService {
/**
* Check whether the active route is using the given path.
*
* @param path Path.
* @return Whether the active route is using the given path.
*/
isCurrent(path: string): boolean {
return this.getCurrentPath() === path;
}
/**
* Get current main menu tab.
*
* @return Current main menu tab or null if the current route is not using the main menu.
*/
getCurrentMainMenuTab(): string | null {
const currentPath = this.getCurrentPath();
const matches = /^\/main\/([^/]+).*$/.exec(currentPath);
return matches?.[1] ?? null;
}
/**
* Navigate to a new path.
*
* @param path Path to navigate to.
* @param options Navigation options.
* @return Whether navigation suceeded.
*/
async navigate(path: string, options: CoreNavigationOptions = {}): Promise<boolean> {
const url: string[] = [/^[./]/.test(path) ? path : `./${path}`];
const navigationOptions: NavigationOptions = CoreObject.withoutEmpty({
animated: options.animated,
queryParams: CoreObject.isEmpty(options.params ?? {}) ? null : options.params,
relativeTo: path.startsWith('/') ? null : this.getCurrentRoute(),
});
const navigationResult = (options.reset ?? false)
? await NavController.instance.navigateRoot(url, navigationOptions)
: await NavController.instance.navigateForward(url, navigationOptions);
return navigationResult !== false;
}
/**
* Navigate to the login credentials route.
*
* @param params Page params.
* @return Whether navigation suceeded.
*/
async navigateToLoginCredentials(params: Params = {}): Promise<boolean> {
// If necessary, open the previous path to keep the navigation history.
if (!this.isCurrent('/login/site') && !this.isCurrent('/login/sites')) {
const hasSites = await CoreSites.instance.hasSites();
await this.navigate(hasSites ? '/login/sites' : '/login/site', { reset: true });
}
// Navigate to login credentials page.
return this.navigate('/login/credentials', { params });
}
/**
* Navigate to the home route of the current site.
*
* @param options Navigation options.
* @return Whether navigation suceeded.
*/
async navigateToSiteHome(options: Omit<CoreNavigationOptions, 'reset'> & { siteId?: string } = {}): Promise<boolean> {
return this.navigateToSitePath(DEFAULT_MAIN_MENU_TAB, options);
}
/**
* Navigate to a site path, loading the site if necessary.
*
* @param path Site path to visit.
* @param options Navigation and site options.
* @return Whether navigation suceeded.
*/
async navigateToSitePath(
path: string,
options: Omit<CoreNavigationOptions, 'reset'> & { siteId?: string } = {},
): Promise<boolean> {
const siteId = options.siteId ?? CoreSites.instance.getCurrentSiteId();
const navigationOptions: CoreNavigationOptions = CoreObject.without(options, ['siteId']);
// @todo: When this function was in ContentLinksHelper, this code was inside NgZone. Check if it's needed.
// If the path doesn't belong to a site, call standard navigation.
if (siteId === CoreConstants.NO_SITE_ID) {
return this.navigate(path, {
...navigationOptions,
reset: true,
});
}
// If we are logged into a different site, log out first.
if (CoreSites.instance.isLoggedIn() && CoreSites.instance.getCurrentSiteId() !== siteId) {
// @todo: Check site plugins and store redirect.
await CoreSites.instance.logout();
}
// If we are not logged into the site, load the site.
if (!CoreSites.instance.isLoggedIn()) {
const modal = await CoreDomUtils.instance.showModalLoading();
try {
const loggedIn = await CoreSites.instance.loadSite(siteId, path, options.params);
if (!loggedIn) {
// User has been redirected to the login page and will be redirected to the site path after login.
return true;
}
} catch (error) {
// Site doesn't exist.
return this.navigate('/login/sites', { reset: true });
} finally {
modal.dismiss();
}
}
// User is logged in, navigate to the site path.
return this.navigateToMainMenuPath(path, navigationOptions);
}
/**
* Get the active route path.
*
* @return Current path.
*/
protected getCurrentPath(): string {
return CoreUrlUtils.instance.removeUrlParams(Router.instance.url);
}
/**
* Get current activated route.
*
* @param route Parent route.
* @return Current activated route.
*/
protected getCurrentRoute(route?: ActivatedRoute): ActivatedRoute {
route = route ?? Router.instance.routerState.root;
return route.children.length === 0 ? route : this.getCurrentRoute(route.children[0]);
}
/**
* Navigate to a path within the main menu.
* If the path belongs to a visible tab, that tab will be selected.
* If it doesn't, the current tab or the default tab will be used instead.
*
* @param options Navigation options.
* @return Whether navigation suceeded.
*/
protected async navigateToMainMenuPath(path: string, options: Omit<CoreNavigationOptions, 'reset'> = {}): Promise<boolean> {
// Due to DeepLinker, we need to remove the path from the URL before going to main menu.
// IonTabs checks the URL to determine which path to load for deep linking, so we clear the URL.
// @todo this.location.replaceState('');
path = path.replace(/^(\.|\/main)?\//, '');
// Open the path within the corresponding main tab.
const pathRoot = /^[^/]+/.exec(path)?.[0] ?? '';
const isCurrentMainMenuHandler = await CoreUtils.instance.ignoreErrors(
CoreMainMenu.instance.isCurrentMainMenuHandler(pathRoot),
false,
);
if (isCurrentMainMenuHandler) {
return this.navigate(`/main/${path}`, options);
}
// Open the path within the current main tab.
const currentMainMenuTab = this.getCurrentMainMenuTab();
if (currentMainMenuTab) {
return this.navigate(`/main/${currentMainMenuTab}/${path}`, options);
}
// Open the path within the default main tab.
// @todo test that this is working as expected
return this.navigate(`/main/${DEFAULT_MAIN_MENU_TAB}`, {
...options,
params: {
redirectPath: `/main/${DEFAULT_MAIN_MENU_TAB}/${path}`,
redirectParams: options.params,
} as CoreRedirectPayload,
});
}
}
export class CoreNavigator extends makeSingleton(CoreNavigatorService) {}

View File

@ -0,0 +1,154 @@
// (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 { NavController as NavControllerService } from '@ionic/angular';
import { mock, mockSingleton } from '@/testing/utils';
import { CoreNavigatorService } from '@services/navigator';
import { CoreUtils, CoreUtilsProvider } from '@services/utils/utils';
import { CoreUrlUtils, CoreUrlUtilsProvider } from '@services/utils/url';
import { NavController, Router } from '@singletons';
import { ActivatedRoute, RouterState } from '@angular/router';
import { CoreSites } from '@services/sites';
import { CoreMainMenu } from '@features/mainmenu/services/mainmenu';
describe('CoreNavigator', () => {
let router: {
url?: string;
routerState?: Partial<RouterState>;
};
let currentMainMenuHandlers: string[];
let navigator: CoreNavigatorService;
let navControllerMock: NavControllerService;
beforeEach(() => {
router = { url: '/' };
currentMainMenuHandlers = ['home'];
navigator = new CoreNavigatorService();
navControllerMock = mockSingleton(NavController, ['navigateRoot', 'navigateForward']);
mockSingleton(Router, router);
mockSingleton(CoreUtils, new CoreUtilsProvider(mock()));
mockSingleton(CoreUrlUtils, new CoreUrlUtilsProvider());
mockSingleton(CoreSites, { getCurrentSiteId: () => 42, isLoggedIn: () => true });
mockSingleton(CoreMainMenu, { isCurrentMainMenuHandler: path => Promise.resolve(currentMainMenuHandlers.includes(path)) });
});
it('matches against current path', () => {
router.url = '/main/foo';
expect(navigator.isCurrent('/main/foo')).toBe(true);
expect(navigator.isCurrent('/main')).toBe(false);
});
it('gets the current main menu tab', () => {
expect(navigator.getCurrentMainMenuTab()).toBeNull();
router.url = '/main/foo';
expect(navigator.getCurrentMainMenuTab()).toBe('foo');
router.url = '/main/foo/bar';
expect(navigator.getCurrentMainMenuTab()).toBe('foo');
});
it('navigates to absolute paths', async () => {
const success = await navigator.navigate('/main/foo/bar', { reset: true });
expect(success).toBe(true);
expect(navControllerMock.navigateRoot).toHaveBeenCalledWith(['/main/foo/bar'], {});
});
it('navigates to relative paths', async () => {
// Arrange.
const mainOutletRoute = { routeConfig: { path: 'foo' }, children: [] };
const primaryOutletRoute = { routeConfig: { path: 'main' }, children: [mainOutletRoute] };
const rootRoute = { children: [primaryOutletRoute] };
router.routerState = { root: rootRoute as unknown as ActivatedRoute };
// Act.
const success = await navigator.navigate('./bar');
// Assert.
expect(success).toBe(true);
expect(navControllerMock.navigateForward).toHaveBeenCalledWith(['./bar'], { relativeTo: mainOutletRoute });
});
it('navigates to site paths', async () => {
// Arrange
router.url = '/main/foo';
// Act
const success = await navigator.navigateToSitePath('/user/42');
// Assert
expect(success).toBe(true);
expect(navControllerMock.navigateForward).toHaveBeenCalledWith(['/main/foo/user/42'], {});
});
it('navigates to site paths using tabs', async () => {
// Arrange
currentMainMenuHandlers.push('users');
// Act
const success = await navigator.navigateToSitePath('/users/user/42');
// Assert
expect(success).toBe(true);
expect(navControllerMock.navigateForward).toHaveBeenCalledWith(['/main/users/user/42'], {});
});
it('navigates to site paths using the default tab', async () => {
const success = await navigator.navigateToSitePath('/user/42');
expect(success).toBe(true);
expect(navControllerMock.navigateForward).toHaveBeenCalledWith(['/main/home'], {
queryParams: {
redirectPath: '/main/home/user/42',
},
});
});
it('navigates to site paths ussing different path formats', async () => {
currentMainMenuHandlers.push('users');
const assertNavigation = async (currentPath, sitePath, expectedPath) => {
router.url = currentPath;
const success = await navigator.navigateToSitePath(sitePath);
expect(success).toBe(true);
expect(navControllerMock.navigateForward).toHaveBeenCalledWith([expectedPath], {});
};
await assertNavigation('/main/users', '/main/users/user/42', '/main/users/user/42');
await assertNavigation('/main/users', '/users/user/42', '/main/users/user/42');
await assertNavigation('/main/users', '/user/42', '/main/users/user/42');
await assertNavigation('/main/home', '/users/user/42', '/main/users/user/42');
});
it('navigates to site home', async () => {
const success = await navigator.navigateToSiteHome();
expect(success).toBe(true);
expect(navControllerMock.navigateForward).toHaveBeenCalledWith(['/main/home'], {});
});
it.todo('navigates to a different site');
it.todo('navigates to login credentials');
it.todo('navigates to NO_SITE_ID site');
});

View File

@ -1580,15 +1580,19 @@ export class CoreUtilsProvider {
* Ignore errors from a promise. * Ignore errors from a promise.
* *
* @param promise Promise to ignore errors. * @param promise Promise to ignore errors.
* @return Promise with ignored errors. * @param fallbackResult Value to return if the promise is rejected.
* @return Promise with ignored errors, resolving to the fallback result if provided.
*/ */
async ignoreErrors<T>(promise: Promise<T>): Promise<T | undefined> { async ignoreErrors<Result>(promise: Promise<Result>): Promise<Result | undefined>;
async ignoreErrors<Result, Fallback>(promise: Promise<Result>, fallback: Fallback): Promise<Result | Fallback>;
async ignoreErrors<Result, Fallback>(promise: Promise<Result>, fallback?: Fallback): Promise<Result | Fallback | undefined> {
try { try {
const result = await promise; const result = await promise;
return result; return result;
} catch (error) { } catch (error) {
// Ignore errors. // Ignore errors.
return fallback;
} }
} }

View File

@ -61,9 +61,7 @@ export class CoreEvents {
static readonly KEYBOARD_CHANGE = 'keyboard_change'; static readonly KEYBOARD_CHANGE = 'keyboard_change';
static readonly CORE_LOADING_CHANGED = 'core_loading_changed'; static readonly CORE_LOADING_CHANGED = 'core_loading_changed';
static readonly ORIENTATION_CHANGE = 'orientation_change'; static readonly ORIENTATION_CHANGE = 'orientation_change';
static readonly LOAD_PAGE_MAIN_MENU = 'load_page_main_menu';
static readonly SEND_ON_ENTER_CHANGED = 'send_on_enter_changed'; static readonly SEND_ON_ENTER_CHANGED = 'send_on_enter_changed';
static readonly MAIN_MENU_OPEN = 'main_menu_open';
static readonly SELECT_COURSE_TAB = 'select_course_tab'; static readonly SELECT_COURSE_TAB = 'select_course_tab';
static readonly WS_CACHE_INVALIDATED = 'ws_cache_invalidated'; static readonly WS_CACHE_INVALIDATED = 'ws_cache_invalidated';
static readonly SITE_STORAGE_DELETED = 'site_storage_deleted'; static readonly SITE_STORAGE_DELETED = 'site_storage_deleted';
@ -227,14 +225,6 @@ export type CoreEventLoadingChangedData = {
uniqueId: string; uniqueId: string;
}; };
/**
* Data passed to LOAD_PAGE_MAIN_MENU event.
*/
export type CoreEventLoadPageMainMenuData = {
redirectPage: string;
redirectParams?: Params;
};
/** /**
* Data passed to COURSE_STATUS_CHANGED event. * Data passed to COURSE_STATUS_CHANGED event.
*/ */

View File

@ -13,6 +13,7 @@
// limitations under the License. // limitations under the License.
import { ApplicationRef, ApplicationInitStatus, Injector, NgZone as NgZoneService, Type } from '@angular/core'; import { ApplicationRef, ApplicationInitStatus, Injector, NgZone as NgZoneService, Type } from '@angular/core';
import { Router as RouterService } from '@angular/router';
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { import {
@ -23,6 +24,7 @@ import {
ToastController as ToastControllerService, ToastController as ToastControllerService,
GestureController as GestureControllerService, GestureController as GestureControllerService,
ActionSheetController as ActionSheetControllerService, ActionSheetController as ActionSheetControllerService,
NavController as NavControllerService,
} from '@ionic/angular'; } from '@ionic/angular';
import { Badge as BadgeService } from '@ionic-native/badge/ngx'; import { Badge as BadgeService } from '@ionic-native/badge/ngx';
@ -150,6 +152,8 @@ export class ToastController extends makeSingleton(ToastControllerService) {}
export class GestureController extends makeSingleton(GestureControllerService) {} export class GestureController extends makeSingleton(GestureControllerService) {}
export class ApplicationInit extends makeSingleton(ApplicationInitStatus) {} export class ApplicationInit extends makeSingleton(ApplicationInitStatus) {}
export class Application extends makeSingleton(ApplicationRef) {} export class Application extends makeSingleton(ApplicationRef) {}
export class NavController extends makeSingleton(NavControllerService) {}
export class Router extends makeSingleton(RouterService) {}
// Convert external libraries injectables. // Convert external libraries injectables.
export class Translate extends makeSingleton(TranslateService) {} export class Translate extends makeSingleton(TranslateService) {}

View File

@ -12,24 +12,60 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
export type CoreObjectWithoutEmpty<T> = {
[k in keyof T]: T[k] extends undefined | null ? never : T[k];
};
/** /**
* Singleton with helper functions for objects. * Singleton with helper functions for objects.
*/ */
export class CoreObject { export class CoreObject {
/** /**
* Delete all keys from an object whose value are null or undefined. * Check whether the given object is empty.
* *
* @param object Object to modify. * @param object Object.
* @return Whether the given object is empty.
*/ */
static removeUndefined<T>(object: T): T { static isEmpty(object: Record<string, unknown>): boolean {
for (const name in object) { return Object.keys(object).length === 0;
if (object[name] === undefined) { }
delete object[name];
} /**
* Create a new object without the specified keys.
*
* @param obj Object.
* @param keys Keys to remove from the new object.
* @return New object without the specified keys.
*/
static without<T, K extends keyof T>(obj: T, keys: K[]): Omit<T, keyof { [k in K]: unknown }> {
const newObject: T = { ...obj };
for (const key of keys) {
delete newObject[key];
} }
return object; return newObject;
}
/**
* Create a new object without empty values (null or undefined).
*
* @param obj Objet.
* @return New object without empty values.
*/
static withoutEmpty<T>(obj: T): CoreObjectWithoutEmpty<T> {
const cleanObj = {};
for (const [key, value] of Object.entries(obj)) {
if (value === null || value === undefined) {
continue;
}
cleanObj[key] = value;
}
return cleanObj as CoreObjectWithoutEmpty<T>;
} }
} }

View File

@ -47,22 +47,26 @@ export function mock<T>(
return instance as T; return instance as T;
} }
export function mockSingleton(singletonClass: CoreSingletonClass<unknown>, instance?: Record<string, unknown>): void; export function mockSingleton<T>(singletonClass: CoreSingletonClass<T>, instance: T): T;
export function mockSingleton( export function mockSingleton<T>(singletonClass: CoreSingletonClass<unknown>, instance?: Record<string, unknown>): T;
export function mockSingleton<T>(
singletonClass: CoreSingletonClass<unknown>, singletonClass: CoreSingletonClass<unknown>,
methods: string[], methods: string[],
instance?: Record<string, unknown>, instance?: Record<string, unknown>,
): void; ): T;
export function mockSingleton( export function mockSingleton<T>(
singletonClass: CoreSingletonClass<unknown>, singletonClass: CoreSingletonClass<T>,
methodsOrInstance: string[] | Record<string, unknown> = [], methodsOrInstance: string[] | Record<string, unknown> = [],
instance: Record<string, unknown> = {}, instance: Record<string, unknown> = {},
): void { ): T {
instance = Array.isArray(methodsOrInstance) ? instance : methodsOrInstance; instance = Array.isArray(methodsOrInstance) ? instance : methodsOrInstance;
const methods = Array.isArray(methodsOrInstance) ? methodsOrInstance : []; const methods = Array.isArray(methodsOrInstance) ? methodsOrInstance : [];
const mockInstance = mock<T>(methods, instance);
singletonClass.setInstance(mock(methods, instance)); singletonClass.setInstance(mockInstance);
return mockInstance;
} }
export async function renderComponent<T>(component: Type<T>, config: Partial<RenderConfig> = {}): Promise<ComponentFixture<T>> { export async function renderComponent<T>(component: Type<T>, config: Partial<RenderConfig> = {}): Promise<ComponentFixture<T>> {