diff --git a/scripts/langindex.json b/scripts/langindex.json index 7381c6fc8..6f1b7f091 100644 --- a/scripts/langindex.json +++ b/scripts/langindex.json @@ -1260,6 +1260,8 @@ "core.completion-alt-manual-y-override": "completion", "core.confirmcanceledit": "local_moodlemobileapp", "core.confirmdeletefile": "repository", + "core.confirmgotabroot": "local_moodlemobileapp", + "core.confirmgotabrootdefault": "local_moodlemobileapp", "core.confirmloss": "local_moodlemobileapp", "core.confirmopeninbrowser": "local_moodlemobileapp", "core.considereddigitalminor": "moodle", diff --git a/src/addon/badges/providers/badge-link-handler.ts b/src/addon/badges/providers/badge-link-handler.ts index bcaf759e0..144acad5c 100644 --- a/src/addon/badges/providers/badge-link-handler.ts +++ b/src/addon/badges/providers/badge-link-handler.ts @@ -15,7 +15,7 @@ import { Injectable } from '@angular/core'; import { CoreContentLinksHandlerBase } from '@core/contentlinks/classes/base-handler'; import { CoreContentLinksAction } from '@core/contentlinks/providers/delegate'; -import { CoreLoginHelperProvider } from '@core/login/providers/helper'; +import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper'; import { AddonBadgesProvider } from './badges'; /** @@ -26,7 +26,7 @@ export class AddonBadgesBadgeLinkHandler extends CoreContentLinksHandlerBase { name = 'AddonBadgesBadgeLinkHandler'; pattern = /\/badges\/badge\.php.*([\?\&]hash=)/; - constructor(private badgesProvider: AddonBadgesProvider, private loginHelper: CoreLoginHelperProvider) { + constructor(private badgesProvider: AddonBadgesProvider, private linkHelper: CoreContentLinksHelperProvider) { super(); } @@ -44,8 +44,7 @@ export class AddonBadgesBadgeLinkHandler extends CoreContentLinksHandlerBase { return [{ action: (siteId, navCtrl?): void => { - // Always use redirect to make it the new history root (to avoid "loops" in history). - this.loginHelper.redirect('AddonBadgesIssuedBadgePage', {courseId: 0, badgeHash: params.hash}, siteId); + this.linkHelper.goInSite(navCtrl, 'AddonBadgesIssuedBadgePage', {courseId: 0, badgeHash: params.hash}, siteId); } }]; } diff --git a/src/addon/badges/providers/mybadges-link-handler.ts b/src/addon/badges/providers/mybadges-link-handler.ts index 8a36cbbc0..190e575ac 100644 --- a/src/addon/badges/providers/mybadges-link-handler.ts +++ b/src/addon/badges/providers/mybadges-link-handler.ts @@ -15,7 +15,7 @@ import { Injectable } from '@angular/core'; import { CoreContentLinksHandlerBase } from '@core/contentlinks/classes/base-handler'; import { CoreContentLinksAction } from '@core/contentlinks/providers/delegate'; -import { CoreLoginHelperProvider } from '@core/login/providers/helper'; +import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper'; import { AddonBadgesProvider } from './badges'; /** @@ -27,7 +27,7 @@ export class AddonBadgesMyBadgesLinkHandler extends CoreContentLinksHandlerBase featureName = 'CoreUserDelegate_AddonBadges'; pattern = /\/badges\/mybadges\.php/; - constructor(private badgesProvider: AddonBadgesProvider, private loginHelper: CoreLoginHelperProvider) { + constructor(private badgesProvider: AddonBadgesProvider, private linkHelper: CoreContentLinksHelperProvider) { super(); } @@ -45,8 +45,7 @@ export class AddonBadgesMyBadgesLinkHandler extends CoreContentLinksHandlerBase return [{ action: (siteId, navCtrl?): void => { - // Always use redirect to make it the new history root (to avoid "loops" in history). - this.loginHelper.redirect('AddonBadgesUserBadgesPage', {}, siteId); + this.linkHelper.goInSite(navCtrl, 'AddonBadgesUserBadgesPage', {}, siteId); } }]; } diff --git a/src/addon/blog/providers/index-link-handler.ts b/src/addon/blog/providers/index-link-handler.ts index 189fad7a3..0670b322b 100644 --- a/src/addon/blog/providers/index-link-handler.ts +++ b/src/addon/blog/providers/index-link-handler.ts @@ -15,7 +15,7 @@ import { Injectable } from '@angular/core'; import { CoreContentLinksHandlerBase } from '@core/contentlinks/classes/base-handler'; import { CoreContentLinksAction } from '@core/contentlinks/providers/delegate'; -import { CoreLoginHelperProvider } from '@core/login/providers/helper'; +import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper'; import { AddonBlogProvider } from './blog'; /** @@ -27,7 +27,7 @@ export class AddonBlogIndexLinkHandler extends CoreContentLinksHandlerBase { featureName = 'CoreUserDelegate_AddonBlog:blogs'; pattern = /\/blog\/index\.php/; - constructor(private blogProvider: AddonBlogProvider, private loginHelper: CoreLoginHelperProvider) { + constructor(private blogProvider: AddonBlogProvider, private linkHelper: CoreContentLinksHelperProvider) { super(); } @@ -53,8 +53,7 @@ export class AddonBlogIndexLinkHandler extends CoreContentLinksHandlerBase { return [{ action: (siteId, navCtrl?): void => { - // Always use redirect to make it the new history root (to avoid "loops" in history). - this.loginHelper.redirect('AddonBlogEntriesPage', pageParams, siteId); + this.linkHelper.goInSite(navCtrl, 'AddonBlogEntriesPage', pageParams, siteId, !Object.keys(pageParams).length); } }]; } diff --git a/src/addon/blog/providers/user-handler.ts b/src/addon/blog/providers/user-handler.ts index 039b9ed56..fe19563b8 100644 --- a/src/addon/blog/providers/user-handler.ts +++ b/src/addon/blog/providers/user-handler.ts @@ -63,7 +63,7 @@ export class AddonBlogUserHandler implements CoreUserProfileHandler { action: (event, navCtrl, user, courseId): void => { event.preventDefault(); event.stopPropagation(); - // Always use redirect to make it the new history root (to avoid "loops" in history). + this.linkHelper.goInSite(navCtrl, 'AddonBlogEntriesPage', { userId: user.id, courseId: courseId }); } }; diff --git a/src/addon/competency/providers/plans-link-handler.ts b/src/addon/competency/providers/plans-link-handler.ts index a99280f3a..da08f33da 100644 --- a/src/addon/competency/providers/plans-link-handler.ts +++ b/src/addon/competency/providers/plans-link-handler.ts @@ -15,7 +15,7 @@ import { Injectable } from '@angular/core'; import { CoreContentLinksHandlerBase } from '@core/contentlinks/classes/base-handler'; import { CoreContentLinksAction } from '@core/contentlinks/providers/delegate'; -import { CoreLoginHelperProvider } from '@core/login/providers/helper'; +import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper'; import { AddonCompetencyProvider } from './competency'; /** @@ -26,7 +26,7 @@ export class AddonCompetencyPlansLinkHandler extends CoreContentLinksHandlerBase name = 'AddonCompetencyPlansLinkHandler'; pattern = /\/admin\/tool\/lp\/plans\.php/; - constructor(private loginHelper: CoreLoginHelperProvider, private competencyProvider: AddonCompetencyProvider) { + constructor(private linkHelper: CoreContentLinksHelperProvider, private competencyProvider: AddonCompetencyProvider) { super(); } @@ -44,8 +44,8 @@ export class AddonCompetencyPlansLinkHandler extends CoreContentLinksHandlerBase return [{ action: (siteId, navCtrl?): void => { - // Always use redirect to make it the new history root (to avoid "loops" in history). - this.loginHelper.redirect('AddonCompetencyPlanListPage', { userId: params.userid }, siteId); + this.linkHelper.goInSite(navCtrl, 'AddonCompetencyPlanListPage', { userId: params.userid }, siteId, + typeof params.userid == 'undefined'); } }]; } diff --git a/src/addon/competency/providers/user-handler.ts b/src/addon/competency/providers/user-handler.ts index 3b04349f6..7cdbaadac 100644 --- a/src/addon/competency/providers/user-handler.ts +++ b/src/addon/competency/providers/user-handler.ts @@ -111,7 +111,7 @@ export class AddonCompetencyUserHandler implements CoreUserProfileHandler { action: (event, navCtrl, user, courseId): void => { event.preventDefault(); event.stopPropagation(); - // Always use redirect to make it the new history root (to avoid "loops" in history). + this.linkHelper.goInSite(navCtrl, 'AddonCompetencyCourseCompetenciesPage', {courseId, userId: user.id}); } }; @@ -123,7 +123,7 @@ export class AddonCompetencyUserHandler implements CoreUserProfileHandler { action: (event, navCtrl, user, courseId): void => { event.preventDefault(); event.stopPropagation(); - // Always use redirect to make it the new history root (to avoid "loops" in history). + this.linkHelper.goInSite(navCtrl, 'AddonCompetencyPlanListPage', {userId: user.id}); } }; diff --git a/src/addon/messages/providers/contact-request-link-handler.ts b/src/addon/messages/providers/contact-request-link-handler.ts index 1c262f601..aebf2e8cd 100644 --- a/src/addon/messages/providers/contact-request-link-handler.ts +++ b/src/addon/messages/providers/contact-request-link-handler.ts @@ -43,7 +43,6 @@ export class AddonMessagesContactRequestLinkHandler extends CoreContentLinksHand CoreContentLinksAction[] | Promise { return [{ action: (siteId, navCtrl?): void => { - // Always use redirect to make it the new history root (to avoid "loops" in history). this.linkHelper.goInSite(navCtrl, 'AddonMessagesContactsPage', {}, siteId); } }]; diff --git a/src/addon/messages/providers/discussion-link-handler.ts b/src/addon/messages/providers/discussion-link-handler.ts index 869e9461b..b916798c6 100644 --- a/src/addon/messages/providers/discussion-link-handler.ts +++ b/src/addon/messages/providers/discussion-link-handler.ts @@ -49,7 +49,6 @@ export class AddonMessagesDiscussionLinkHandler extends CoreContentLinksHandlerB const stateParams = { userId: parseInt(params.id || params.user2, 10) }; - // Always use redirect to make it the new history root (to avoid "loops" in history). this.linkHelper.goInSite(navCtrl, 'AddonMessagesDiscussionPage', stateParams, siteId); } }]; diff --git a/src/addon/messages/providers/index-link-handler.ts b/src/addon/messages/providers/index-link-handler.ts index 974c6ddef..400bd4eb1 100644 --- a/src/addon/messages/providers/index-link-handler.ts +++ b/src/addon/messages/providers/index-link-handler.ts @@ -49,7 +49,6 @@ export class AddonMessagesIndexLinkHandler extends CoreContentLinksHandlerBase { pageName = 'AddonMessagesGroupConversationsPage'; } - // Always use redirect to make it the new history root (to avoid "loops" in history). this.linkHelper.goInSite(navCtrl, pageName, undefined, siteId); } }]; diff --git a/src/addon/messages/providers/user-send-message-handler.ts b/src/addon/messages/providers/user-send-message-handler.ts index a80b90ade..8fd8982d9 100644 --- a/src/addon/messages/providers/user-send-message-handler.ts +++ b/src/addon/messages/providers/user-send-message-handler.ts @@ -76,7 +76,6 @@ export class AddonMessagesSendMessageUserHandler implements CoreUserProfileHandl showKeyboard: true, userId: user.id }; - // Always use redirect to make it the new history root (to avoid "loops" in history). this.linkHelper.goInSite(navCtrl, 'AddonMessagesDiscussionPage', pageParams); } }; diff --git a/src/addon/mod/book/providers/link-handler.ts b/src/addon/mod/book/providers/link-handler.ts index 899978d4b..631885cbe 100644 --- a/src/addon/mod/book/providers/link-handler.ts +++ b/src/addon/mod/book/providers/link-handler.ts @@ -46,7 +46,7 @@ export class AddonModBookLinkHandler extends CoreContentLinksModuleIndexHandler return [{ action: (siteId, navCtrl?): void => { this.courseHelper.navigateToModule(parseInt(params.id, 10), siteId, courseId, undefined, - this.useModNameToGetModule ? this.modName : undefined, modParams); + this.useModNameToGetModule ? this.modName : undefined, modParams, navCtrl); } }]; } diff --git a/src/addon/mod/lesson/providers/grade-link-handler.ts b/src/addon/mod/lesson/providers/grade-link-handler.ts index f71c50586..f76eb8b52 100644 --- a/src/addon/mod/lesson/providers/grade-link-handler.ts +++ b/src/addon/mod/lesson/providers/grade-link-handler.ts @@ -70,7 +70,7 @@ export class AddonModLessonGradeLinkHandler extends CoreContentLinksModuleGradeH this.linkHelper.goInSite(navCtrl, 'AddonModLessonUserRetakePage', pageParams, siteId); } else { // User cannot view the report, go to lesson index. - this.courseHelper.navigateToModule(moduleId, siteId, courseId, module.section); + this.courseHelper.navigateToModule(moduleId, siteId, courseId, module.section, undefined, undefined, navCtrl); } }).catch((error) => { this.domUtils.showErrorModalDefault(error, 'core.course.errorgetmodule', true); diff --git a/src/addon/mod/lesson/providers/index-link-handler.ts b/src/addon/mod/lesson/providers/index-link-handler.ts index 244c55fcd..16d77cd6e 100644 --- a/src/addon/mod/lesson/providers/index-link-handler.ts +++ b/src/addon/mod/lesson/providers/index-link-handler.ts @@ -19,6 +19,7 @@ import { CoreContentLinksAction } from '@core/contentlinks/providers/delegate'; import { CoreCourseProvider } from '@core/course/providers/course'; import { CoreCourseHelperProvider } from '@core/course/providers/helper'; import { AddonModLessonProvider } from './lesson'; +import { NavController } from 'ionic-angular'; /** * Handler to treat links to lesson index. @@ -51,9 +52,10 @@ export class AddonModLessonIndexLinkHandler extends CoreContentLinksModuleIndexH /* Ignore the pageid param. If we open the lesson player with a certain page and the user hasn't started the lesson, an error is thrown: could not find lesson_timer records. */ if (params.userpassword) { - this.navigateToModuleWithPassword(parseInt(params.id, 10), courseId, params.userpassword, siteId); + this.navigateToModuleWithPassword(parseInt(params.id, 10), courseId, params.userpassword, siteId, navCtrl); } else { - this.courseHelper.navigateToModule(parseInt(params.id, 10), siteId, courseId); + this.courseHelper.navigateToModule(parseInt(params.id, 10), siteId, courseId, + undefined, undefined, undefined, navCtrl); } } }]; @@ -80,9 +82,11 @@ export class AddonModLessonIndexLinkHandler extends CoreContentLinksModuleIndexH * @param {number} courseId Course ID. * @param {string} password Password. * @param {string} siteId Site ID. + * @param {NavController} navCtrl Navigation controller. * @return {Promise} Promise resolved when navigated. */ - protected navigateToModuleWithPassword(moduleId: number, courseId: number, password: string, siteId: string): Promise { + protected navigateToModuleWithPassword(moduleId: number, courseId: number, password: string, siteId: string, + navCtrl?: NavController): Promise { const modal = this.domUtils.showModalLoading(); // Get the module. @@ -93,11 +97,12 @@ export class AddonModLessonIndexLinkHandler extends CoreContentLinksModuleIndexH return this.lessonProvider.storePassword(parseInt(module.instance, 10), password, siteId).catch(() => { // Ignore errors. }).then(() => { - return this.courseHelper.navigateToModule(moduleId, siteId, courseId, module.section); + return this.courseHelper.navigateToModule(moduleId, siteId, courseId, module.section, + undefined, undefined, navCtrl); }); }).catch(() => { // Error, go to index page. - return this.courseHelper.navigateToModule(moduleId, siteId, courseId); + return this.courseHelper.navigateToModule(moduleId, siteId, courseId, undefined, undefined, undefined, navCtrl); }).finally(() => { modal.dismiss(); }); diff --git a/src/addon/notes/providers/user-handler.ts b/src/addon/notes/providers/user-handler.ts index c7a37d061..cd9ce8015 100644 --- a/src/addon/notes/providers/user-handler.ts +++ b/src/addon/notes/providers/user-handler.ts @@ -99,7 +99,7 @@ export class AddonNotesUserHandler implements CoreUserProfileHandler { action: (event, navCtrl, user, courseId): void => { event.preventDefault(); event.stopPropagation(); - // Always use redirect to make it the new history root (to avoid "loops" in history). + this.linkHelper.goInSite(navCtrl, 'AddonNotesListPage', { userId: user.id, courseId: courseId }); } }; diff --git a/src/assets/lang/en.json b/src/assets/lang/en.json index 7ed0d4812..6069bde0b 100644 --- a/src/assets/lang/en.json +++ b/src/assets/lang/en.json @@ -1260,6 +1260,8 @@ "core.completion-alt-manual-y-override": "Completed: {{$a.modname}} (set by {{$a.overrideuser}}). Select to mark as not complete.", "core.confirmcanceledit": "Are you sure you want to leave this page? All changes will be lost.", "core.confirmdeletefile": "Are you sure you want to delete this file?", + "core.confirmgotabroot": "Are you sure you want to go back to {{name}}?", + "core.confirmgotabrootdefault": "Are you sure you want to go to the initial page of the current tab?", "core.confirmloss": "Are you sure? All changes will be lost.", "core.confirmopeninbrowser": "Do you want to open it in a web browser?", "core.considereddigitalminor": "You are too young to create an account on this site.", diff --git a/src/components/ion-tabs/core-ion-tabs.html b/src/components/ion-tabs/core-ion-tabs.html index 123d340c1..5a4dda4b3 100644 --- a/src/components/ion-tabs/core-ion-tabs.html +++ b/src/components/ion-tabs/core-ion-tabs.html @@ -1,5 +1,5 @@
- +
diff --git a/src/components/ion-tabs/ion-tabs.ts b/src/components/ion-tabs/ion-tabs.ts index deeb76a53..914d97ab6 100644 --- a/src/components/ion-tabs/ion-tabs.ts +++ b/src/components/ion-tabs/ion-tabs.ts @@ -20,11 +20,14 @@ import { import { CoreIonTabComponent } from './ion-tab'; import { CoreUtilsProvider, PromiseDefer } from '@providers/utils/utils'; import { CoreAppProvider } from '@providers/app'; +import { CoreDomUtilsProvider } from '@providers/utils/dom'; +import { TranslateService } from '@ngx-translate/core'; /** - * Equivalent to ion-tabs. It has 2 improvements: + * Equivalent to ion-tabs. It has several improvements: * - If a core-ion-tab is added or removed, it will be reflected in the tab bar in the right position. * - It supports a loaded input to tell when are the tabs ready. + * - When the user clicks the tab again to go to root, a confirm modal is shown. */ @Component({ selector: 'core-ion-tabs', @@ -73,7 +76,8 @@ export class CoreIonTabsComponent extends Tabs implements OnDestroy { constructor(protected utils: CoreUtilsProvider, protected appProvider: CoreAppProvider, @Optional() parent: NavController, @Optional() viewCtrl: ViewController, _app: App, config: Config, elementRef: ElementRef, _plt: Platform, - renderer: Renderer, _linker: DeepLinker, keyboard?: Keyboard) { + renderer: Renderer, _linker: DeepLinker, protected domUtils: CoreDomUtilsProvider, + protected translate: TranslateService, keyboard?: Keyboard) { super(parent, viewCtrl, _app, config, elementRef, _plt, renderer, _linker, keyboard); } @@ -272,13 +276,25 @@ export class CoreIonTabsComponent extends Tabs implements OnDestroy { * * @param {number|Tab} tabOrIndex Index, or the Tab instance, of the tab to select. * @param {NavOptions} Nav options. - * @param {boolean} [fromUrl=true] Whether to load from a URL. + * @param {boolean} [fromUrl] Whether to load from a URL. + * @param {boolean} [manualClick] Whether the user manually clicked the tab. * @return {Promise} Promise resolved when selected. */ - select(tabOrIndex: number | Tab, opts: NavOptions = {}, fromUrl: boolean = false): Promise { + select(tabOrIndex: number | Tab, opts: NavOptions = {}, fromUrl?: boolean, manualClick?: boolean): Promise { if (this.initialized) { // Tabs have been initialized, select the tab. + if (manualClick) { + // If we'll go to the root of the current tab, ask the user to confirm first. + const tab = typeof tabOrIndex == 'number' ? this.getByIndex(tabOrIndex) : tabOrIndex; + + return this.confirmGoToRoot(tab).then(() => { + return super.select(tabOrIndex, opts, fromUrl); + }, () => { + // User cancelled. + }); + } + return super.select(tabOrIndex, opts, fromUrl); } else { // Tabs not initialized yet. Mark it as "selectedIndex" input so it's treated when the tabs are initialized. @@ -305,11 +321,16 @@ export class CoreIonTabsComponent extends Tabs implements OnDestroy { if (this.initialized) { const tab = this.getByIndex(index); if (tab) { - return tab.goToRoot({animate: false, updateUrl: true, isNavRoot: true}).then(() => { - // Tab not previously selected. Select it after going to root. - if (!tab.isSelected) { - return this.select(tab, {animate: false, updateUrl: true, isNavRoot: true}); - } + return this.confirmGoToRoot(tab).then(() => { + // User confirmed, go to root. + return tab.goToRoot({animate: tab.isSelected, updateUrl: true, isNavRoot: true}).then(() => { + // Tab not previously selected. Select it after going to root. + if (!tab.isSelected) { + return this.select(tab, {animate: false, updateUrl: true, isNavRoot: true}); + } + }); + }, () => { + // User cancelled. }); } @@ -349,4 +370,23 @@ export class CoreIonTabsComponent extends Tabs implements OnDestroy { // Unregister the custom back button action for this page this.unregisterBackButtonAction && this.unregisterBackButtonAction(); } + + /** + * Confirm if the user wants to go to the root of the current tab. + * + * @param {Tab} tab Tab to go to root. + * @return {Promise} Promise resolved when confirmed. + */ + confirmGoToRoot(tab: Tab): Promise { + if (!tab || !tab.isSelected || (tab.getActive() && tab.getActive().isFirst())) { + // Tab not selected or is already at root, no need to confirm. + return Promise.resolve(); + } else { + if (tab.tabTitle) { + return this.domUtils.showConfirm(this.translate.instant('core.confirmgotabroot', {name: tab.tabTitle})); + } else { + return this.domUtils.showConfirm(this.translate.instant('core.confirmgotabrootdefault')); + } + } + } } diff --git a/src/core/contentlinks/classes/module-grade-handler.ts b/src/core/contentlinks/classes/module-grade-handler.ts index a97b30a2a..c1a5124e4 100644 --- a/src/core/contentlinks/classes/module-grade-handler.ts +++ b/src/core/contentlinks/classes/module-grade-handler.ts @@ -77,7 +77,7 @@ export class CoreContentLinksModuleGradeHandler extends CoreContentLinksHandlerB if (!params.userid || params.userid == site.getUserId()) { // No user specified or current user. Navigate to module. this.courseHelper.navigateToModule(parseInt(params.id, 10), siteId, courseId, undefined, - this.useModNameToGetModule ? this.modName : undefined); + this.useModNameToGetModule ? this.modName : undefined, undefined, navCtrl); } else if (this.canReview) { // Use the goToReview function. this.goToReview(url, params, courseId, siteId, navCtrl); diff --git a/src/core/contentlinks/classes/module-index-handler.ts b/src/core/contentlinks/classes/module-index-handler.ts index fbb65afba..67159b012 100644 --- a/src/core/contentlinks/classes/module-index-handler.ts +++ b/src/core/contentlinks/classes/module-index-handler.ts @@ -60,7 +60,7 @@ export class CoreContentLinksModuleIndexHandler extends CoreContentLinksHandlerB return [{ action: (siteId, navCtrl?): void => { this.courseHelper.navigateToModule(parseInt(params.id, 10), siteId, courseId, undefined, - this.useModNameToGetModule ? this.modName : undefined); + this.useModNameToGetModule ? this.modName : undefined, undefined, navCtrl); } }]; } diff --git a/src/core/contentlinks/classes/module-list-handler.ts b/src/core/contentlinks/classes/module-list-handler.ts index 5fa6c41a7..37e44e7d7 100644 --- a/src/core/contentlinks/classes/module-list-handler.ts +++ b/src/core/contentlinks/classes/module-list-handler.ts @@ -63,7 +63,6 @@ export class CoreContentLinksModuleListHandler extends CoreContentLinksHandlerBa title: this.title || this.translate.instant('addon.mod_' + this.modName + '.modulenameplural') }; - // Always use redirect to make it the new history root (to avoid "loops" in history). this.linkHelper.goInSite(navCtrl, 'CoreCourseListModTypePage', stateParams, siteId); } }]; diff --git a/src/core/contentlinks/providers/helper.ts b/src/core/contentlinks/providers/helper.ts index bfd8b47b2..6e9f77e91 100644 --- a/src/core/contentlinks/providers/helper.ts +++ b/src/core/contentlinks/providers/helper.ts @@ -30,6 +30,7 @@ import { CoreConstants } from '@core/constants'; import { CoreConfigConstants } from '../../../configconstants'; import { CoreSitePluginsProvider } from '@core/siteplugins/providers/siteplugins'; import { CoreSite } from '@classes/site'; +import { CoreMainMenuProvider } from '@core/mainmenu/providers/mainmenu'; /** * Service that provides some features regarding content links. @@ -42,7 +43,8 @@ export class CoreContentLinksHelperProvider { private contentLinksDelegate: CoreContentLinksDelegate, private appProvider: CoreAppProvider, private domUtils: CoreDomUtilsProvider, private urlUtils: CoreUrlUtilsProvider, private translate: TranslateService, private initDelegate: CoreInitDelegate, eventsProvider: CoreEventsProvider, private textUtils: CoreTextUtilsProvider, - private sitePluginsProvider: CoreSitePluginsProvider, private zone: NgZone, private utils: CoreUtilsProvider) { + private sitePluginsProvider: CoreSitePluginsProvider, private zone: NgZone, private utils: CoreUtilsProvider, + private mainMenuProvider: CoreMainMenuProvider) { this.logger = logger.getInstance('CoreContentLinksHelperProvider'); } @@ -103,9 +105,10 @@ export class CoreContentLinksHelperProvider { * @param {string} pageName Name of the page to go. * @param {any} [pageParams] Params to send to the page. * @param {string} [siteId] Site ID. If not defined, current site. + * @param {boolean} [checkMenu] If true, check if the root page of a main menu tab. Only the page name will be checked. * @return {Promise} Promise resolved when done. */ - goInSite(navCtrl: NavController, pageName: string, pageParams: any, siteId?: string): Promise { + goInSite(navCtrl: NavController, pageName: string, pageParams: any, siteId?: string, checkMenu?: boolean): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); const deferred = this.utils.promiseDefer(); @@ -113,7 +116,23 @@ export class CoreContentLinksHelperProvider { // Execute the code in the Angular zone, so change detection doesn't stop working. this.zone.run(() => { if (navCtrl && siteId == this.sitesProvider.getCurrentSiteId()) { - navCtrl.push(pageName, pageParams).then(deferred.resolve, deferred.reject); + if (checkMenu) { + // Check if the page is in the main menu. + this.mainMenuProvider.isCurrentMainMenuHandler(pageName, pageParams).catch(() => { + return false; // Shouldn't happen. + }).then((isInMenu) => { + if (isInMenu) { + // Just select the tab. + this.loginHelper.loadPageInMainMenu(pageName, pageParams); + + deferred.resolve(); + } else { + navCtrl.push(pageName, pageParams).then(deferred.resolve, deferred.reject); + } + }); + } else { + navCtrl.push(pageName, pageParams).then(deferred.resolve, deferred.reject); + } } else { this.loginHelper.redirect(pageName, pageParams, siteId).then(deferred.resolve, deferred.reject); } diff --git a/src/core/course/classes/main-resource-component.ts b/src/core/course/classes/main-resource-component.ts index b2a153add..e357d0888 100644 --- a/src/core/course/classes/main-resource-component.ts +++ b/src/core/course/classes/main-resource-component.ts @@ -245,7 +245,6 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy, * @param {any} event Event. */ gotoBlog(event: any): void { - // Always use redirect to make it the new history root (to avoid "loops" in history). this.linkHelper.goInSite(this.navCtrl, 'AddonBlogEntriesPage', { cmId: this.module.id }); } diff --git a/src/core/course/components/format/format.ts b/src/core/course/components/format/format.ts index 1c87d2aed..f296406f5 100644 --- a/src/core/course/components/format/format.ts +++ b/src/core/course/components/format/format.ts @@ -76,12 +76,14 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy { loaded: boolean; protected sectionStatusObserver; + protected selectTabObserver; protected lastCourseFormat: string; constructor(private cfDelegate: CoreCourseFormatDelegate, translate: TranslateService, private injector: Injector, private courseHelper: CoreCourseHelperProvider, private domUtils: CoreDomUtilsProvider, eventsProvider: CoreEventsProvider, private sitesProvider: CoreSitesProvider, private content: Content, - prefetchDelegate: CoreCourseModulePrefetchDelegate, private modalCtrl: ModalController) { + prefetchDelegate: CoreCourseModulePrefetchDelegate, private modalCtrl: ModalController, + private courseProvider: CoreCourseProvider) { this.selectOptions.title = translate.instant('core.course.sections'); this.completionChanged = new EventEmitter(); @@ -124,6 +126,28 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy { }); } }, this.sitesProvider.getCurrentSiteId()); + + // Listen for select course tab events to select the right section if needed. + this.selectTabObserver = eventsProvider.on(CoreEventsProvider.SELECT_COURSE_TAB, (data) => { + + if (!data.name) { + let section; + + if (typeof data.sectionId != 'undefined' && data.sectionId != null && this.sections) { + section = this.sections.find((section) => { + return section.id == data.sectionId; + }); + } else if (typeof data.sectionNumber != 'undefined' && data.sectionNumber != null && this.sections) { + section = this.sections.find((section) => { + return section.section == data.sectionNumber; + }); + } + + if (section) { + this.sectionChanged(section); + } + } + }); } /** @@ -312,6 +336,13 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy { } else { this.domUtils.scrollToTop(this.content, 0); } + + if (!previousValue || previousValue.id != newSection.id) { + // First load or section changed, add log in Moodle. + this.courseProvider.logView(this.course.id, newSection.section, undefined, this.course.fullname).catch(() => { + // Ignore errors. + }); + } } /** @@ -437,9 +468,8 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy { * Component destroyed. */ ngOnDestroy(): void { - if (this.sectionStatusObserver) { - this.sectionStatusObserver.off(); - } + this.sectionStatusObserver && this.sectionStatusObserver.off(); + this.selectTabObserver && this.selectTabObserver.off(); } /** diff --git a/src/core/course/pages/section/section.ts b/src/core/course/pages/section/section.ts index c2d67c3f1..efbd0d748 100644 --- a/src/core/course/pages/section/section.ts +++ b/src/core/course/pages/section/section.ts @@ -67,6 +67,7 @@ export class CoreCourseSectionPage implements OnDestroy { protected modParams: any; protected completionObserver; protected courseStatusObserver; + protected selectTabObserver; protected syncObserver; protected firstTabName: string; protected isDestroyed = false; @@ -120,6 +121,26 @@ export class CoreCourseSectionPage implements OnDestroy { } }, sitesProvider.getCurrentSiteId()); } + + this.selectTabObserver = eventsProvider.on(CoreEventsProvider.SELECT_COURSE_TAB, (data) => { + + if (!data.name) { + // If needed, set sectionId and sectionNumber. They'll only be used if the content tabs hasn't been loaded yet. + this.sectionId = data.sectionId || this.sectionId; + this.sectionNumber = data.sectionNumber || this.sectionNumber; + + // Select course contents. + this.tabsComponent && this.tabsComponent.selectTab(0); + } else if (this.courseHandlers) { + const index = this.courseHandlers.findIndex((handler) => { + return handler.name == data.name; + }); + + if (index >= 0) { + this.tabsComponent && this.tabsComponent.selectTab(index + 1); + } + } + }); } /** @@ -213,11 +234,6 @@ export class CoreCourseSectionPage implements OnDestroy { }).then((sections) => { let promise; - // Add log in Moodle. - this.courseProvider.logView(this.course.id, this.sectionNumber, undefined, this.course.fullname).catch(() => { - // Ignore errors. - }); - // Get the completion status. if (this.course.enablecompletion === false) { // Completion not enabled. @@ -483,9 +499,8 @@ export class CoreCourseSectionPage implements OnDestroy { */ ngOnDestroy(): void { this.isDestroyed = true; - if (this.completionObserver) { - this.completionObserver.off(); - } + this.completionObserver && this.completionObserver.off(); + this.selectTabObserver && this.selectTabObserver.off(); } /** diff --git a/src/core/course/providers/course.ts b/src/core/course/providers/course.ts index bb4a55c5a..ae8ab44df 100644 --- a/src/core/course/providers/course.ts +++ b/src/core/course/providers/course.ts @@ -164,6 +164,23 @@ export class CoreCourseProvider { }); } + /** + * Check if the current view in a NavController is a certain course initial page. + * + * @param {NavController} navCtrl NavController. + * @param {number} courseId Course ID. + * @return {boolean} Whether the current view is a certain course. + */ + currentViewIsCourse(navCtrl: NavController, courseId: number): boolean { + if (navCtrl) { + const view = navCtrl.getActive(); + + return view && view.id == 'CoreCourseSectionPage' && view.data && view.data.course && view.data.course.id == courseId; + } + + return false; + } + /** * Get completion status of all the activities in a course for a certain user. * @@ -973,6 +990,19 @@ export class CoreCourseProvider { }); } + /** + * Select a certain tab in the course. Please use currentViewIsCourse() first to verify user is viewing the course. + * + * @param {string} [name] Name of the tab. If not provided, course contents. + * @param {any} [params] Other params. + */ + selectCourseTab(name?: string, params?: any): void { + params = params || {}; + params.name = name || ''; + + this.eventsProvider.trigger(CoreEventsProvider.SELECT_COURSE_TAB, params); + } + /** * Change the course status, setting it to the previous status. * diff --git a/src/core/course/providers/helper.ts b/src/core/course/providers/helper.ts index 0a7b95247..c458b999c 100644 --- a/src/core/course/providers/helper.ts +++ b/src/core/course/providers/helper.ts @@ -36,6 +36,7 @@ import { CoreCourseModulePrefetchDelegate } from './module-prefetch-delegate'; import { CoreLoginHelperProvider } from '@core/login/providers/helper'; import { CoreConstants } from '@core/constants'; import { CoreSite } from '@classes/site'; +import { CoreLoggerProvider } from '@providers/logger'; import * as moment from 'moment'; /** @@ -115,16 +116,21 @@ export type CoreCourseCoursesProgress = { export class CoreCourseHelperProvider { protected courseDwnPromises: { [s: string]: { [id: number]: Promise } } = {}; + protected logger; constructor(private courseProvider: CoreCourseProvider, private domUtils: CoreDomUtilsProvider, - private moduleDelegate: CoreCourseModuleDelegate, private prefetchDelegate: CoreCourseModulePrefetchDelegate, - private filepoolProvider: CoreFilepoolProvider, private sitesProvider: CoreSitesProvider, - private textUtils: CoreTextUtilsProvider, private timeUtils: CoreTimeUtilsProvider, - private utils: CoreUtilsProvider, private translate: TranslateService, private loginHelper: CoreLoginHelperProvider, - private courseOptionsDelegate: CoreCourseOptionsDelegate, private siteHomeProvider: CoreSiteHomeProvider, - private eventsProvider: CoreEventsProvider, private fileHelper: CoreFileHelperProvider, - private appProvider: CoreAppProvider, private fileProvider: CoreFileProvider, private injector: Injector, - private coursesProvider: CoreCoursesProvider, private courseOffline: CoreCourseOfflineProvider) { } + private moduleDelegate: CoreCourseModuleDelegate, private prefetchDelegate: CoreCourseModulePrefetchDelegate, + private filepoolProvider: CoreFilepoolProvider, private sitesProvider: CoreSitesProvider, + private textUtils: CoreTextUtilsProvider, private timeUtils: CoreTimeUtilsProvider, + private utils: CoreUtilsProvider, private translate: TranslateService, private loginHelper: CoreLoginHelperProvider, + private courseOptionsDelegate: CoreCourseOptionsDelegate, private siteHomeProvider: CoreSiteHomeProvider, + private eventsProvider: CoreEventsProvider, private fileHelper: CoreFileHelperProvider, + private appProvider: CoreAppProvider, private fileProvider: CoreFileProvider, private injector: Injector, + private coursesProvider: CoreCoursesProvider, private courseOffline: CoreCourseOfflineProvider, + loggerProvider: CoreLoggerProvider) { + + this.logger = loggerProvider.getInstance('CoreCourseHelperProvider'); + } /** * This function treats every module on the sections provided to load the handler data, treat completion @@ -1109,9 +1115,12 @@ export class CoreCourseHelperProvider { * @param {string} [modName] If set, the app will retrieve all modules of this type with a single WS call. This reduces the * number of WS calls, but it isn't recommended for modules that can return a lot of contents. * @param {any} [modParams] Params to pass to the module + * @param {NavController} [navCtrl] NavController for adding new pages to the current history. Optional for legacy support, but + * generates a warning if omitted. * @return {Promise} Promise resolved when done. */ - navigateToModule(moduleId: number, siteId?: string, courseId?: number, sectionId?: number, modName?: string, modParams?: any) + navigateToModule(moduleId: number, siteId?: string, courseId?: number, sectionId?: number, modName?: string, modParams?: any, + navCtrl?: NavController) : Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -1157,6 +1166,16 @@ export class CoreCourseHelperProvider { module.handlerData = this.moduleDelegate.getModuleDataFor(module.modname, module, courseId, sectionId); + if (navCtrl) { + // If the link handler for this module passed through navCtrl, we can use the module's handler to navigate cleanly. + // Otherwise, we will redirect below. + modal.dismiss(); + + return module.handlerData.action(new Event('click'), navCtrl, module, courseId); + } + + this.logger.warn('navCtrl was not passed to navigateToModule by the link handler for ' + module.modname); + if (courseId == site.getSiteHomeId()) { // Check if site home is available. return this.siteHomeProvider.isAvailable().then(() => { diff --git a/src/core/courses/providers/course-link-handler.ts b/src/core/courses/providers/course-link-handler.ts index 010dcdbb2..5ffe35cb6 100644 --- a/src/core/courses/providers/course-link-handler.ts +++ b/src/core/courses/providers/course-link-handler.ts @@ -22,6 +22,8 @@ import { CoreContentLinksAction } from '@core/contentlinks/providers/delegate'; import { CoreCourseProvider } from '@core/course/providers/course'; import { CoreCourseHelperProvider } from '@core/course/providers/helper'; import { CoreCoursesProvider } from './courses'; +import { NavController } from 'ionic-angular'; +import { CoreLoggerProvider } from '@providers/logger'; /** * Handler to treat links to course view or enrol (except site home). @@ -32,12 +34,16 @@ export class CoreCoursesCourseLinkHandler extends CoreContentLinksHandlerBase { pattern = /((\/enrol\/index\.php)|(\/course\/enrol\.php)|(\/course\/view\.php)).*([\?\&]id=\d+)/; protected waitStart = 0; + protected logger; constructor(private sitesProvider: CoreSitesProvider, private coursesProvider: CoreCoursesProvider, private domUtils: CoreDomUtilsProvider, private translate: TranslateService, private courseProvider: CoreCourseProvider, - private textUtils: CoreTextUtilsProvider, private courseHelper: CoreCourseHelperProvider) { + private textUtils: CoreTextUtilsProvider, private courseHelper: CoreCourseHelperProvider, + loggerProvider: CoreLoggerProvider) { super(); + + this.logger = loggerProvider.getInstance('CoreCoursesCourseLinkHandler'); } /** @@ -75,9 +81,17 @@ export class CoreCoursesCourseLinkHandler extends CoreContentLinksHandlerBase { action: (siteId, navCtrl?): void => { siteId = siteId || this.sitesProvider.getCurrentSiteId(); if (siteId == this.sitesProvider.getCurrentSiteId()) { - this.actionEnrol(courseId, url, pageParams).catch(() => { - // Ignore errors. - }); + // Check if we already are in the course index page. + if (this.courseProvider.currentViewIsCourse(navCtrl, courseId)) { + // Current view is this course, just select the contents tab. + this.courseProvider.selectCourseTab('', pageParams); + + return; + } else { + this.actionEnrol(courseId, url, pageParams, navCtrl).catch(() => { + // Ignore errors. + }); + } } else { // Don't pass the navCtrl to make the course the new history root (to avoid "loops" in history). this.courseHelper.getAndOpenCourse(undefined, courseId, pageParams, siteId); @@ -115,9 +129,11 @@ export class CoreCoursesCourseLinkHandler extends CoreContentLinksHandlerBase { * @param {number} courseId Course ID. * @param {string} url Treated URL. * @param {any} pageParams Params to send to the new page. + * @param {NavController} [navCtrl] NavController for adding new pages to the current history. Optional for legacy support, but + * generates a warning if omitted. * @return {Promise} Promise resolved when done. */ - protected actionEnrol(courseId: number, url: string, pageParams: any): Promise { + protected actionEnrol(courseId: number, url: string, pageParams: any, navCtrl?: NavController): Promise { const modal = this.domUtils.showModalLoading(), isEnrolUrl = !!url.match(/(\/enrol\/index\.php)|(\/course\/enrol\.php)/); let course; @@ -188,8 +204,12 @@ export class CoreCoursesCourseLinkHandler extends CoreContentLinksHandlerBase { }).then((course) => { modal.dismiss(); + if (typeof navCtrl === 'undefined') { + this.logger.warn('navCtrl was not passed to actionEnrol'); + } + // Now open the course. - this.courseHelper.openCourse(undefined, course, pageParams); + this.courseHelper.openCourse(navCtrl, course, pageParams); }); } diff --git a/src/core/courses/providers/courses-index-link-handler.ts b/src/core/courses/providers/courses-index-link-handler.ts index 834629eac..bbea7ec34 100644 --- a/src/core/courses/providers/courses-index-link-handler.ts +++ b/src/core/courses/providers/courses-index-link-handler.ts @@ -15,7 +15,7 @@ import { Injectable } from '@angular/core'; import { CoreContentLinksHandlerBase } from '@core/contentlinks/classes/base-handler'; import { CoreContentLinksAction } from '@core/contentlinks/providers/delegate'; -import { CoreLoginHelperProvider } from '@core/login/providers/helper'; +import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper'; import { CoreCoursesProvider } from './courses'; /** @@ -27,7 +27,7 @@ export class CoreCoursesIndexLinkHandler extends CoreContentLinksHandlerBase { featureName = 'CoreMainMenuDelegate_CoreCourses'; pattern = /\/course\/?(index\.php.*)?$/; - constructor(private coursesProvider: CoreCoursesProvider, private loginHelper: CoreLoginHelperProvider) { + constructor(private coursesProvider: CoreCoursesProvider, private linkHelper: CoreContentLinksHelperProvider) { super(); } @@ -56,8 +56,7 @@ export class CoreCoursesIndexLinkHandler extends CoreContentLinksHandlerBase { } } - // Always use redirect to make it the new history root (to avoid "loops" in history). - this.loginHelper.redirect(page, pageParams, siteId); + this.linkHelper.goInSite(navCtrl, page, pageParams, siteId); } }]; } diff --git a/src/core/courses/providers/dashboard-link-handler.ts b/src/core/courses/providers/dashboard-link-handler.ts index 26f00164e..aaa0d7ab5 100644 --- a/src/core/courses/providers/dashboard-link-handler.ts +++ b/src/core/courses/providers/dashboard-link-handler.ts @@ -43,7 +43,7 @@ export class CoreCoursesDashboardLinkHandler extends CoreContentLinksHandlerBase CoreContentLinksAction[] | Promise { return [{ action: (siteId, navCtrl?): void => { - // Always use redirect to make it the new history root (to avoid "loops" in history). + // Use redirect to select the tab. this.loginHelper.redirect('CoreCoursesDashboardPage', undefined, siteId); } }]; diff --git a/src/core/grades/providers/helper.ts b/src/core/grades/providers/helper.ts index 5df58c9dd..22d925f6a 100644 --- a/src/core/grades/providers/helper.ts +++ b/src/core/grades/providers/helper.ts @@ -24,7 +24,6 @@ import { CoreUrlUtilsProvider } from '@providers/utils/url'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper'; -import { CoreLoginHelperProvider } from '@core/login/providers/helper'; import { CoreCourseHelperProvider } from '@core/course/providers/helper'; /** @@ -38,8 +37,7 @@ export class CoreGradesHelperProvider { private gradesProvider: CoreGradesProvider, private sitesProvider: CoreSitesProvider, private textUtils: CoreTextUtilsProvider, private courseProvider: CoreCourseProvider, private domUtils: CoreDomUtilsProvider, private urlUtils: CoreUrlUtilsProvider, private utils: CoreUtilsProvider, - private linkHelper: CoreContentLinksHelperProvider, private loginHelper: CoreLoginHelperProvider, - private courseHelper: CoreCourseHelperProvider) { + private linkHelper: CoreContentLinksHelperProvider, private courseHelper: CoreCourseHelperProvider) { this.logger = logger.getInstance('CoreGradesHelperProvider'); } @@ -457,14 +455,22 @@ export class CoreGradesHelperProvider { }); } - // View own grades. Open the course with the grades tab selected. + // View own grades. Check if we already are in the course index page. + if (this.courseProvider.currentViewIsCourse(navCtrl, courseId)) { + // Current view is this course, just select the grades tab. + this.courseProvider.selectCourseTab('CoreGrades'); + + return; + } + + // Open the course with the grades tab selected. return this.courseHelper.getCourse(courseId, siteId).then((result) => { const pageParams: any = { course: result.course, selectedTab: 'CoreGrades' }; - return this.loginHelper.redirect('CoreCourseSectionPage', pageParams, siteId).catch(() => { + return this.linkHelper.goInSite(navCtrl, 'CoreCourseSectionPage', pageParams, siteId).catch(() => { // Ignore errors. }); }); diff --git a/src/core/grades/providers/overview-link-handler.ts b/src/core/grades/providers/overview-link-handler.ts index 32ab28ac1..1f34a82b0 100644 --- a/src/core/grades/providers/overview-link-handler.ts +++ b/src/core/grades/providers/overview-link-handler.ts @@ -43,7 +43,6 @@ export class CoreGradesOverviewLinkHandler extends CoreContentLinksHandlerBase { CoreContentLinksAction[] | Promise { return [{ action: (siteId, navCtrl?): void => { - // Always use redirect to make it the new history root (to avoid "loops" in history). this.linkHelper.goInSite(navCtrl, 'CoreGradesCoursesPage', undefined, siteId); } }]; diff --git a/src/core/grades/providers/user-handler.ts b/src/core/grades/providers/user-handler.ts index 1b8e61de9..20d1d3a49 100644 --- a/src/core/grades/providers/user-handler.ts +++ b/src/core/grades/providers/user-handler.ts @@ -112,7 +112,6 @@ export class CoreGradesUserHandler implements CoreUserProfileHandler { courseId: courseId, userId: user.id }; - // Always use redirect to make it the new history root (to avoid "loops" in history). this.linkHelper.goInSite(navCtrl, 'CoreGradesCoursePage', pageParams); } }; diff --git a/src/core/login/providers/helper.ts b/src/core/login/providers/helper.ts index 2d4bbc62f..dd54ecac6 100644 --- a/src/core/login/providers/helper.ts +++ b/src/core/login/providers/helper.ts @@ -629,7 +629,7 @@ export class CoreLoginHelperProvider { * @param {string} page Name of the page to load. * @param {any} params Params to pass to the page. */ - protected loadPageInMainMenu(page: string, params: any): void { + loadPageInMainMenu(page: string, params: any): void { if (!this.appProvider.isMainMenuOpen()) { // Main menu not open. Store the page to be loaded later. this.pageToLoad = { diff --git a/src/core/mainmenu/providers/mainmenu.ts b/src/core/mainmenu/providers/mainmenu.ts index 5f8fe59ed..d601956c4 100644 --- a/src/core/mainmenu/providers/mainmenu.ts +++ b/src/core/mainmenu/providers/mainmenu.ts @@ -16,7 +16,9 @@ import { Injectable } from '@angular/core'; import { NavController } from 'ionic-angular'; import { CoreLangProvider } from '@providers/lang'; import { CoreSitesProvider } from '@providers/sites'; +import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreConfigConstants } from '../../../configconstants'; +import { CoreMainMenuDelegate, CoreMainMenuHandlerToDisplay } from './delegate'; /** * Custom main menu item. @@ -56,10 +58,34 @@ export class CoreMainMenuProvider { static ITEM_MIN_WIDTH = 72; // Min with of every item, based on 5 items on a 360 pixel wide screen. protected tablet = false; - constructor(private langProvider: CoreLangProvider, private sitesProvider: CoreSitesProvider) { + constructor(private langProvider: CoreLangProvider, private sitesProvider: CoreSitesProvider, + protected menuDelegate: CoreMainMenuDelegate, protected utils: CoreUtilsProvider) { this.tablet = window && window.innerWidth && window.innerWidth >= 576 && window.innerHeight >= 576; } + /** + * Get the current main menu handlers. + * + * @return {Promise} Promise resolved with the current main menu handlers. + */ + getCurrentMainMenuHandlers(): Promise { + const deferred = this.utils.promiseDefer(); + + const subscription = this.menuDelegate.getHandlers().subscribe((handlers) => { + subscription && subscription.unsubscribe(); + + // Remove the handlers that should only appear in the More menu. + handlers = handlers.filter((handler) => { + return !handler.onlyInMore; + }); + + // Return main handlers. + deferred.resolve(handlers.slice(0, this.getNumItems())); + }); + + return deferred.promise; + } + /** * Get a list of custom menu items for a certain site. * @@ -211,6 +237,23 @@ export class CoreMainMenuProvider { return tablet ? 'side' : 'bottom'; } + /** + * Check if a certain page is the root of a main menu handler currently displayed. + * + * @param {string} page Name of the page. + * @param {string} [pageParams] Page params. + * @return {Promise} Promise resolved with boolean: whether it's the root of a main menu handler. + */ + isCurrentMainMenuHandler(pageName: string, pageParams?: any): Promise { + return this.getCurrentMainMenuHandlers().then((handlers) => { + const handler = handlers.find((handler, i) => { + return handler.page == pageName; + }); + + return !!handler; + }); + } + /** * Check if responsive main menu items is disabled in the current site. * diff --git a/src/core/sitehome/providers/index-link-handler.ts b/src/core/sitehome/providers/index-link-handler.ts index eb2cb5906..f0e15ad31 100644 --- a/src/core/sitehome/providers/index-link-handler.ts +++ b/src/core/sitehome/providers/index-link-handler.ts @@ -16,7 +16,7 @@ import { Injectable } from '@angular/core'; import { CoreSitesProvider } from '@providers/sites'; import { CoreContentLinksHandlerBase } from '@core/contentlinks/classes/base-handler'; import { CoreContentLinksAction } from '@core/contentlinks/providers/delegate'; -import { CoreLoginHelperProvider } from '@core/login/providers/helper'; +import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper'; import { CoreSiteHomeProvider } from './sitehome'; /** @@ -29,7 +29,7 @@ export class CoreSiteHomeIndexLinkHandler extends CoreContentLinksHandlerBase { pattern = /\/course\/view\.php.*([\?\&]id=\d+)/; constructor(private sitesProvider: CoreSitesProvider, private siteHomeProvider: CoreSiteHomeProvider, - private loginHelper: CoreLoginHelperProvider) { + private linkHelper: CoreContentLinksHelperProvider) { super(); } @@ -46,8 +46,7 @@ export class CoreSiteHomeIndexLinkHandler extends CoreContentLinksHandlerBase { CoreContentLinksAction[] | Promise { return [{ action: (siteId, navCtrl?): void => { - // Always use redirect to make it the new history root (to avoid "loops" in history). - this.loginHelper.redirect('CoreSiteHomeIndexPage', undefined, siteId); + this.linkHelper.goInSite(navCtrl, 'CoreSiteHomeIndexPage', undefined, siteId); } }]; } diff --git a/src/core/user/providers/participants-link-handler.ts b/src/core/user/providers/participants-link-handler.ts index b9bb3eddd..fb9971f02 100644 --- a/src/core/user/providers/participants-link-handler.ts +++ b/src/core/user/providers/participants-link-handler.ts @@ -16,7 +16,7 @@ import { Injectable } from '@angular/core'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreContentLinksHandlerBase } from '@core/contentlinks/classes/base-handler'; import { CoreContentLinksAction } from '@core/contentlinks/providers/delegate'; -import { CoreLoginHelperProvider } from '@core/login/providers/helper'; +import { CoreCourseProvider } from '@core/course/providers/course'; import { CoreCourseHelperProvider } from '@core/course/providers/helper'; import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper'; import { CoreUserProvider } from './user'; @@ -30,9 +30,9 @@ export class CoreUserParticipantsLinkHandler extends CoreContentLinksHandlerBase featureName = 'CoreCourseOptionsDelegate_CoreUserParticipants'; pattern = /\/user\/index\.php/; - constructor(private userProvider: CoreUserProvider, private loginHelper: CoreLoginHelperProvider, + constructor(private userProvider: CoreUserProvider, private courseHelper: CoreCourseHelperProvider, private domUtils: CoreDomUtilsProvider, - private linkHelper: CoreContentLinksHelperProvider) { + private linkHelper: CoreContentLinksHelperProvider, private courseProvider: CoreCourseProvider) { super(); } @@ -51,6 +51,14 @@ export class CoreUserParticipantsLinkHandler extends CoreContentLinksHandlerBase return [{ action: (siteId, navCtrl?): void => { + // Check if we already are in the course index page. + if (this.courseProvider.currentViewIsCourse(navCtrl, courseId)) { + // Current view is this course, just select the participants tab. + this.courseProvider.selectCourseTab('CoreUserParticipants'); + + return; + } + const modal = this.domUtils.showModalLoading(); this.courseHelper.getCourse(courseId, siteId).then((result) => { @@ -59,8 +67,9 @@ export class CoreUserParticipantsLinkHandler extends CoreContentLinksHandlerBase selectedTab: 'CoreUserParticipants' }; - // Always use redirect to make it the new history root (to avoid "loops" in history). - return this.loginHelper.redirect('CoreCourseSectionPage', params, siteId); + return this.linkHelper.goInSite(navCtrl, 'CoreCourseSectionPage', params, siteId).catch(() => { + // Ignore errors. + }); }).catch(() => { // Cannot get course for some reason, just open the participants page. return this.linkHelper.goInSite(navCtrl, 'CoreUserParticipantsPage', {courseId: courseId}, siteId); diff --git a/src/core/user/providers/user-link-handler.ts b/src/core/user/providers/user-link-handler.ts index faaa55c81..eb9a0b0b6 100644 --- a/src/core/user/providers/user-link-handler.ts +++ b/src/core/user/providers/user-link-handler.ts @@ -47,7 +47,6 @@ export class CoreUserProfileLinkHandler extends CoreContentLinksHandlerBase { courseId: params.course, userId: parseInt(params.id, 10) }; - // Always use redirect to make it the new history root (to avoid "loops" in history). this.linkHelper.goInSite(navCtrl, 'CoreUserProfilePage', pageParams, siteId); } }]; diff --git a/src/lang/en.json b/src/lang/en.json index bf2f305e5..fe9102832 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -40,6 +40,8 @@ "completion-alt-manual-y-override": "Completed: {{$a.modname}} (set by {{$a.overrideuser}}). Select to mark as not complete.", "confirmcanceledit": "Are you sure you want to leave this page? All changes will be lost.", "confirmdeletefile": "Are you sure you want to delete this file?", + "confirmgotabroot": "Are you sure you want to go back to {{name}}?", + "confirmgotabrootdefault": "Are you sure you want to go to the initial page of the current tab?", "confirmloss": "Are you sure? All changes will be lost.", "confirmopeninbrowser": "Do you want to open it in a web browser?", "considereddigitalminor": "You are too young to create an account on this site.", diff --git a/src/providers/events.ts b/src/providers/events.ts index 650cc55e8..fe75a177a 100644 --- a/src/providers/events.ts +++ b/src/providers/events.ts @@ -60,6 +60,7 @@ export class CoreEventsProvider { static LOAD_PAGE_MAIN_MENU = 'load_page_main_menu'; static SEND_ON_ENTER_CHANGED = 'send_on_enter_changed'; static MAIN_MENU_OPEN = 'main_menu_open'; + static SELECT_COURSE_TAB = 'select_course_tab'; protected logger; protected observables: { [s: string]: Subject } = {};