diff --git a/src/addons/addons.module.ts b/src/addons/addons.module.ts index 1f507b5b8..85b2f0917 100644 --- a/src/addons/addons.module.ts +++ b/src/addons/addons.module.ts @@ -20,6 +20,7 @@ import { AddonFilterModule } from './filter/filter.module'; import { AddonUserProfileFieldModule } from './userprofilefield/userprofilefield.module'; import { AddonBadgesModule } from './badges/badges.module'; import { AddonCalendarModule } from './calendar/calendar.module'; +import { AddonCourseCompletionModule } from './coursecompletion/coursecompletion.module'; import { AddonNotificationsModule } from './notifications/notifications.module'; import { AddonMessageOutputModule } from './messageoutput/messageoutput.module'; import { AddonMessagesModule } from './messages/messages.module'; @@ -35,6 +36,7 @@ import { AddonRemoteThemesModule } from './remotethemes/remotethemes.module'; AddonBadgesModule, AddonBlogModule, AddonCalendarModule, + AddonCourseCompletionModule, AddonMessagesModule, AddonPrivateFilesModule, AddonFilterModule, diff --git a/src/addons/badges/badges-lazy.module.ts b/src/addons/badges/badges-lazy.module.ts index e23e2d26c..bd6cd1392 100644 --- a/src/addons/badges/badges-lazy.module.ts +++ b/src/addons/badges/badges-lazy.module.ts @@ -19,8 +19,8 @@ import { conditionalRoutes } from '@/app/app-routing.module'; import { CoreScreen } from '@services/screen'; import { CoreSharedModule } from '@/core/shared.module'; -import { AddonBadgesIssuedBadgePage } from './pages/issued-badge/issued-badge'; -import { AddonBadgesUserBadgesPage } from './pages/user-badges/user-badges'; +import { AddonBadgesIssuedBadgePage } from './pages/issued-badge/issued-badge.page'; +import { AddonBadgesUserBadgesPage } from './pages/user-badges/user-badges.page'; const mobileRoutes: Routes = [ { diff --git a/src/addons/badges/pages/issued-badge/issued-badge.ts b/src/addons/badges/pages/issued-badge/issued-badge.page.ts similarity index 100% rename from src/addons/badges/pages/issued-badge/issued-badge.ts rename to src/addons/badges/pages/issued-badge/issued-badge.page.ts diff --git a/src/addons/badges/pages/user-badges/user-badges.ts b/src/addons/badges/pages/user-badges/user-badges.page.ts similarity index 91% rename from src/addons/badges/pages/user-badges/user-badges.ts rename to src/addons/badges/pages/user-badges/user-badges.page.ts index 79c621e77..4a145fb4b 100644 --- a/src/addons/badges/pages/user-badges/user-badges.ts +++ b/src/addons/badges/pages/user-badges/user-badges.page.ts @@ -20,8 +20,9 @@ import { CoreDomUtils } from '@services/utils/dom'; import { CoreSites } from '@services/sites'; import { CoreUtils } from '@services/utils/utils'; import { CorePageItemsListManager } from '@classes/page-items-list-manager'; -import { ActivatedRoute, ActivatedRouteSnapshot, Params } from '@angular/router'; +import { ActivatedRouteSnapshot, Params } from '@angular/router'; import { CoreSplitViewComponent } from '@components/split-view/split-view'; +import { CoreNavigator } from '@services/navigator'; /** * Page that displays the list of calendar events. @@ -37,9 +38,9 @@ export class AddonBadgesUserBadgesPage implements AfterViewInit, OnDestroy { @ViewChild(CoreSplitViewComponent) splitView!: CoreSplitViewComponent; - constructor(route: ActivatedRoute) { - const courseId = parseInt(route.snapshot.queryParams.courseId ?? 0); // Use 0 for site badges. - const userId = parseInt(route.snapshot.queryParams.userId ?? CoreSites.getCurrentSiteUserId()); + constructor() { + const courseId = CoreNavigator.getRouteNumberParam('courseId') ?? 0; // Use 0 for site badges. + const userId = CoreNavigator.getRouteNumberParam('userId') ?? CoreSites.getCurrentSiteUserId(); this.badges = new AddonBadgesUserBadgesManager(AddonBadgesUserBadgesPage, courseId, userId); } diff --git a/src/addons/badges/services/handlers/user.ts b/src/addons/badges/services/handlers/user.ts index f2cb27bac..52e44fb69 100644 --- a/src/addons/badges/services/handlers/user.ts +++ b/src/addons/badges/services/handlers/user.ts @@ -14,7 +14,6 @@ import { Injectable } from '@angular/core'; import { CoreCourseUserAdminOrNavOptionIndexed } from '@features/courses/services/courses'; -import { CoreUserProfile } from '@features/user/services/user'; import { CoreUserDelegateService, CoreUserProfileHandler, CoreUserProfileHandlerData } from '@features/user/services/user-delegate'; import { CoreNavigator } from '@services/navigator'; import { makeSingleton } from '@singletons'; @@ -42,13 +41,11 @@ export class AddonBadgesUserHandlerService implements CoreUserProfileHandler { /** * Check if handler is enabled for this user in this context. * - * @param user User to check. * @param courseId Course ID. * @param navOptions Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions. * @return True if enabled, false otherwise. */ - async isEnabledForUser( - user: CoreUserProfile, + async isEnabledForCourse( courseId: number, navOptions?: CoreCourseUserAdminOrNavOptionIndexed, ): Promise { diff --git a/src/addons/block/activitymodules/components/activitymodules/activitymodules.ts b/src/addons/block/activitymodules/components/activitymodules/activitymodules.ts index 373344396..c377040b5 100644 --- a/src/addons/block/activitymodules/components/activitymodules/activitymodules.ts +++ b/src/addons/block/activitymodules/components/activitymodules/activitymodules.ts @@ -131,9 +131,8 @@ export class AddonBlockActivityModulesComponent extends CoreBlockBaseComponent i * @param entry Selected entry. */ gotoCoureListModType(entry: AddonBlockActivityModuleEntry): void { - CoreNavigator.navigateToSitePath('course/list-mod-type', { + CoreNavigator.navigateToSitePath('course/' + this.getCourseId() + '/list-mod-type', { params: { - courseId: this.getCourseId(), modName: entry.modName, title: entry.name, }, diff --git a/src/addons/block/completionstatus/services/block-handler.ts b/src/addons/block/completionstatus/services/block-handler.ts index b62f94926..602be93b2 100644 --- a/src/addons/block/completionstatus/services/block-handler.ts +++ b/src/addons/block/completionstatus/services/block-handler.ts @@ -29,21 +29,14 @@ export class AddonBlockCompletionStatusHandlerService extends CoreBlockBaseHandl blockName = 'completionstatus'; /** - * Returns the data needed to render the block. - * - * @param block The block to render. - * @param contextLevel The context where the block will be used. - * @param instanceId The instance ID associated with the context level. - * @return Data or promise resolved with the data. + * @inheritdoc */ getDisplayData(block: CoreCourseBlock, contextLevel: string, instanceId: number): CoreBlockHandlerData { - // @todo - return { title: 'addon.block_completionstatus.pluginname', class: 'addon-block-completion-status', component: CoreBlockOnlyTitleComponent, - link: 'AddonCourseCompletionReportPage', + link: 'coursecompletion', linkParams: { courseId: instanceId, }, diff --git a/src/addons/block/selfcompletion/services/block-handler.ts b/src/addons/block/selfcompletion/services/block-handler.ts index e8a8e6783..01bed3b35 100644 --- a/src/addons/block/selfcompletion/services/block-handler.ts +++ b/src/addons/block/selfcompletion/services/block-handler.ts @@ -29,22 +29,17 @@ export class AddonBlockSelfCompletionHandlerService extends CoreBlockBaseHandler blockName = 'selfcompletion'; /** - * Returns the data needed to render the block. - * - * @param block The block to render. - * @param contextLevel The context where the block will be used. - * @param instanceId The instance ID associated with the context level. - * @return Data or promise resolved with the data. + * @inheritdoc */ getDisplayData(block: CoreCourseBlock, contextLevel: string, instanceId: number): CoreBlockHandlerData { - // @todo - return { title: 'addon.block_selfcompletion.pluginname', class: 'addon-block-self-completion', component: CoreBlockOnlyTitleComponent, - link: 'AddonCourseCompletionReportPage', - linkParams: { courseId: instanceId }, + link: 'coursecompletion', + linkParams: { + courseId: instanceId, + }, }; } diff --git a/src/addons/blog/services/handlers/user.ts b/src/addons/blog/services/handlers/user.ts index f2e625f58..5c6d08726 100644 --- a/src/addons/blog/services/handlers/user.ts +++ b/src/addons/blog/services/handlers/user.ts @@ -35,13 +35,6 @@ export class AddonBlogUserHandlerService implements CoreUserProfileHandler { return AddonBlog.isPluginEnabled(); } - /** - * @inheritdoc - */ - async isEnabledForUser(): Promise { - return true; - } - /** * @inheritdoc */ diff --git a/src/addons/coursecompletion/coursecompletion-lazy.module.ts b/src/addons/coursecompletion/coursecompletion-lazy.module.ts new file mode 100644 index 000000000..2231a59b8 --- /dev/null +++ b/src/addons/coursecompletion/coursecompletion-lazy.module.ts @@ -0,0 +1,41 @@ +// (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 { CoreSharedModule } from '@/core/shared.module'; +import { NgModule } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; +import { CoreCommentsComponentsModule } from '@features/comments/components/components.module'; +import { CoreTagComponentsModule } from '@features/tag/components/components.module'; +import { AddonCourseCompletionReportPage } from './pages/report/report'; + +const routes: Routes = [ + { + path: '', + component: AddonCourseCompletionReportPage, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + CoreSharedModule, + CoreCommentsComponentsModule, + CoreTagComponentsModule, + ], + exports: [RouterModule], + declarations: [ + AddonCourseCompletionReportPage, + ], +}) +export class AddonCourseCompletionLazyModule {} diff --git a/src/addons/coursecompletion/coursecompletion.module.ts b/src/addons/coursecompletion/coursecompletion.module.ts index ccc8af630..024980c8b 100644 --- a/src/addons/coursecompletion/coursecompletion.module.ts +++ b/src/addons/coursecompletion/coursecompletion.module.ts @@ -12,33 +12,42 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { NgModule } from '@angular/core'; -// @todo import { AddonCourseCompletionCourseOptionHandler } from './services/course-option-handler'; -// @todo import { AddonCourseCompletionUserHandler } from './services/user-handler'; -// @todo import { AddonCourseCompletionComponentsModule } from './components/components.module'; -// @todo import { CoreCourseOptionsDelegate } from '@features/course/services/options-delegate'; -// @todo import { CoreUserDelegate } from '@features/user/services/user-delegate'; +import { APP_INITIALIZER, NgModule, Type } from '@angular/core'; +import { Routes } from '@angular/router'; +import { CoreCourseIndexRoutingModule } from '@features/course/pages/index/index-routing.module'; +import { CoreCourseOptionsDelegate } from '@features/course/services/course-options-delegate'; +import { CoreMainMenuTabRoutingModule } from '@features/mainmenu/mainmenu-tab-routing.module'; +import { CoreUserDelegate } from '@features/user/services/user-delegate'; +import { AddonCourseCompletionProvider } from './services/coursecompletion'; +import { AddonCourseCompletionCourseOptionHandler } from './services/handlers/course-option'; +import { AddonCourseCompletionUserHandler } from './services/handlers/user'; + +export const ADDON_COURSECOMPLETION_SERVICES: Type[] = [ + AddonCourseCompletionProvider, +]; + +const routes: Routes = [ + { + path: 'coursecompletion', + loadChildren: () => import('./coursecompletion-lazy.module').then(m => m.AddonCourseCompletionLazyModule), + }, +]; @NgModule({ imports: [ - // AddonCourseCompletionComponentsModule, + CoreMainMenuTabRoutingModule.forChild(routes), + CoreCourseIndexRoutingModule.forChild({ children: routes }), ], providers: [ - // AddonCourseCompletionCourseOptionHandler, - // AddonCourseCompletionUserHandler, + { + provide: APP_INITIALIZER, + multi: true, + deps: [], + useFactory: () => async () => { + CoreUserDelegate.registerHandler(AddonCourseCompletionUserHandler.instance); + CoreCourseOptionsDelegate.registerHandler(AddonCourseCompletionCourseOptionHandler.instance); + }, + }, ], }) -export class AddonCourseCompletionModule { - - /* @todo constructor( - courseOptionsDelegate: CoreCourseOptionsDelegate, - courseOptionHandler: AddonCourseCompletionCourseOptionHandler, - userDelegate: CoreUserDelegate, - userHandler: AddonCourseCompletionUserHandler, - ) { - // Register handlers. - courseOptionsDelegate.registerHandler(courseOptionHandler); - userDelegate.registerHandler(userHandler); - }*/ - -} +export class AddonCourseCompletionModule {} diff --git a/src/addons/coursecompletion/pages/report/report.html b/src/addons/coursecompletion/pages/report/report.html new file mode 100644 index 000000000..cff8b4432 --- /dev/null +++ b/src/addons/coursecompletion/pages/report/report.html @@ -0,0 +1,92 @@ + + + + + + {{ 'addon.coursecompletion.coursecompletion' | translate }} + + + + + + + + + + +

{{ 'addon.coursecompletion.status' | translate }}

+

{{ statusText! | translate }}

+
+
+ + +

{{ 'addon.coursecompletion.required' | translate }}

+

{{ 'addon.coursecompletion.criteriarequiredall' | translate }}

+

{{ 'addon.coursecompletion.criteriarequiredany' | translate }}

+
+
+
+ + + {{ 'addon.coursecompletion.requiredcriteria' | translate }} + + + +

+

+
+ {{ criteria.status }} +
+ + + + {{ 'addon.coursecompletion.criteriagroup' | translate }} + {{ 'addon.coursecompletion.criteria' | translate }} + {{ 'addon.coursecompletion.requirement' | translate }} + {{ 'addon.coursecompletion.status' | translate }} + {{ 'addon.coursecompletion.complete' | translate }} + {{ 'addon.coursecompletion.completiondate' | translate }} + + + + + + + + + + + + + + + {{ criteria.status }} + + {{ criteria.timecompleted * 1000 | coreFormatDate :'strftimedatetimeshort' }} + + + + + +
+ + + {{ 'addon.coursecompletion.manualselfcompletion' | translate }} + + + + + {{ 'addon.coursecompletion.completecourse' | translate }} + + + + + + + + + {{ 'addon.coursecompletion.nottracked' | translate }} + + +
+
diff --git a/src/addons/coursecompletion/pages/report/report.ts b/src/addons/coursecompletion/pages/report/report.ts new file mode 100644 index 000000000..5cda867eb --- /dev/null +++ b/src/addons/coursecompletion/pages/report/report.ts @@ -0,0 +1,112 @@ +// (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 { + AddonCourseCompletion, + AddonCourseCompletionCourseCompletionStatus, +} from '@addons/coursecompletion/services/coursecompletion'; +import { Component, OnInit } from '@angular/core'; +import { IonRefresher } from '@ionic/angular'; +import { CoreNavigator } from '@services/navigator'; +import { CoreSites } from '@services/sites'; +import { CoreDomUtils } from '@services/utils/dom'; + +/** + * Page that displays the course completion report. + */ +@Component({ + selector: 'page-addon-course-completion-report', + templateUrl: 'report.html', +}) +export class AddonCourseCompletionReportPage implements OnInit { + + protected courseId!: number; + protected userId!: number; + + completionLoaded = false; + completion?: AddonCourseCompletionCourseCompletionStatus; + showSelfComplete = false; + tracked = true; // Whether completion is tracked. + statusText?: string; + + /** + * @inheritdoc + */ + ngOnInit(): void { + this.courseId = CoreNavigator.getRouteNumberParam('courseId')!; + this.userId = CoreNavigator.getRouteNumberParam('userId') || CoreSites.getCurrentSiteUserId(); + + if (!this.userId) { + this.userId = CoreSites.getCurrentSiteUserId(); + } + + this.fetchCompletion().finally(() => { + this.completionLoaded = true; + }); + } + + /** + * Fetch compleiton data. + * + * @return Promise resolved when done. + */ + protected async fetchCompletion(): Promise { + try { + this.completion = await AddonCourseCompletion.getCompletion(this.courseId, this.userId); + + this.statusText = AddonCourseCompletion.getCompletedStatusText(this.completion); + this.showSelfComplete = AddonCourseCompletion.canMarkSelfCompleted(this.userId, this.completion); + + this.tracked = true; + } catch (error) { + if (error && error.errorcode == 'notenroled') { + // Not enrolled error, probably a teacher. + this.tracked = false; + } else { + CoreDomUtils.showErrorModalDefault(error, 'addon.coursecompletion.couldnotloadreport', true); + } + } + } + + /** + * Refresh completion data on PTR. + * + * @param refresher Refresher instance. + */ + async refreshCompletion(refresher?: IonRefresher): Promise { + await AddonCourseCompletion.invalidateCourseCompletion(this.courseId, this.userId).finally(() => { + this.fetchCompletion().finally(() => { + refresher?.complete(); + }); + }); + } + + /** + * Mark course as completed. + */ + async completeCourse(): Promise { + const modal = await CoreDomUtils.showModalLoading('core.sending', true); + + try { + await AddonCourseCompletion.markCourseAsSelfCompleted(this.courseId); + + await this.refreshCompletion(); + } catch (error) { + CoreDomUtils.showErrorModal(error); + } finally { + modal.dismiss(); + } + } + +} diff --git a/src/addons/coursecompletion/services/coursecompletion.ts b/src/addons/coursecompletion/services/coursecompletion.ts index cee944029..21898f3c6 100644 --- a/src/addons/coursecompletion/services/coursecompletion.ts +++ b/src/addons/coursecompletion/services/coursecompletion.ts @@ -164,9 +164,9 @@ export class AddonCourseCompletionProvider { * @param preferCache True if shouldn't call WS if data is cached, false otherwise. * @return Promise resolved with true if plugin is enabled, rejected or resolved with false otherwise. */ - async isPluginViewEnabledForCourse(courseId: number, preferCache: boolean = true): Promise { + async isPluginViewEnabledForCourse(courseId?: number, preferCache: boolean = true): Promise { if (!courseId) { - throw new CoreError('No courseId provided'); + return false; } const course = await CoreCourses.getUserCourse(courseId, preferCache); diff --git a/src/addons/coursecompletion/services/handlers/course-option.ts b/src/addons/coursecompletion/services/handlers/course-option.ts new file mode 100644 index 000000000..7fbef1c50 --- /dev/null +++ b/src/addons/coursecompletion/services/handlers/course-option.ts @@ -0,0 +1,96 @@ +// (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 { CoreCourseProvider } from '@features/course/services/course'; +import { + CoreCourseAccess, + CoreCourseOptionsHandler, + CoreCourseOptionsHandlerData, +} from '@features/course/services/course-options-delegate'; +import { CoreEnrolledCourseDataWithExtraInfoAndOptions } from '@features/courses/services/courses-helper'; +import { makeSingleton } from '@singletons'; +import { AddonCourseCompletion } from '../coursecompletion'; + +/** + * Handler to inject an option into the course main menu. + */ +@Injectable({ providedIn: 'root' }) +export class AddonCourseCompletionCourseOptionHandlerService implements CoreCourseOptionsHandler { + + name = 'AddonCourseCompletion'; + priority = 200; + + /** + * @inheritdoc + */ + async isEnabled(): Promise { + return AddonCourseCompletion.isPluginViewEnabled(); + } + + /** + * @inheritdoc + */ + async isEnabledForCourse(courseId: number, accessData: CoreCourseAccess): Promise { + if (accessData && accessData.type == CoreCourseProvider.ACCESS_GUEST) { + return false; // Not enabled for guests. + } + + const courseEnabled = await AddonCourseCompletion.isPluginViewEnabledForCourse(courseId); + // If is not enabled in the course, is not enabled for the user. + if (!courseEnabled) { + return false; + } + + return AddonCourseCompletion.isPluginViewEnabledForUser(courseId); + } + + /** + * @inheritdoc + */ + getDisplayData(): CoreCourseOptionsHandlerData | Promise { + return { + title: 'addon.coursecompletion.completionmenuitem', + class: 'addon-coursecompletion-course-handler', + page: 'coursecompletion', + }; + } + + /** + * @inheritdoc + */ + async invalidateEnabledForCourse(courseId: number): Promise { + await AddonCourseCompletion.invalidateCourseCompletion(courseId); + } + + /** + * @inheritdoc + */ + async prefetch(course: CoreEnrolledCourseDataWithExtraInfoAndOptions): Promise { + try { + await AddonCourseCompletion.getCompletion(course.id, undefined, { + getFromCache: false, + emergencyCache: false, + }); + } catch (error) { + if (error && error.errorcode == 'notenroled') { + // Not enrolled error, probably a teacher. Ignore error. + } else { + throw error; + } + } + } + +} +export const AddonCourseCompletionCourseOptionHandler = makeSingleton(AddonCourseCompletionCourseOptionHandlerService); diff --git a/src/addons/coursecompletion/services/handlers/user.ts b/src/addons/coursecompletion/services/handlers/user.ts new file mode 100644 index 000000000..172073c60 --- /dev/null +++ b/src/addons/coursecompletion/services/handlers/user.ts @@ -0,0 +1,73 @@ +// (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 { CoreUserProfile } from '@features/user/services/user'; +import { CoreUserProfileHandler, CoreUserDelegateService, CoreUserProfileHandlerData } from '@features/user/services/user-delegate'; +import { CoreNavigator } from '@services/navigator'; +import { makeSingleton } from '@singletons'; +import { AddonCourseCompletion } from '../coursecompletion'; + +/** + * Profile course completion handler. + */ +@Injectable({ providedIn: 'root' }) +export class AddonCourseCompletionUserHandlerService implements CoreUserProfileHandler { + + name = 'AddonCourseCompletion'; + type = CoreUserDelegateService.TYPE_NEW_PAGE; + priority = 200; + cacheEnabled = true; + + /** + * @inheritdoc + */ + async isEnabled(): Promise { + return AddonCourseCompletion.isPluginViewEnabled(); + } + + /** + * @inheritdoc + */ + async isEnabledForCourse(courseId?: number): Promise { + return AddonCourseCompletion.isPluginViewEnabledForCourse(courseId); + } + + /** + * @inheritdoc + */ + async isEnabledForUser(user: CoreUserProfile, courseId?: number): Promise { + return await AddonCourseCompletion.isPluginViewEnabledForUser(courseId!, user.id); + } + + /** + * @inheritdoc + */ + getDisplayData(): CoreUserProfileHandlerData { + return { + icon: 'fas-tasks', + title: 'addon.coursecompletion.coursecompletion', + class: 'addon-coursecompletion-handler', + action: (event, user, courseId): void => { + event.preventDefault(); + event.stopPropagation(); + CoreNavigator.navigateToSitePath('/coursecompletion', { + params: { courseId, userId: user.id }, + }); + }, + }; + } + +} +export const AddonCourseCompletionUserHandler = makeSingleton(AddonCourseCompletionUserHandlerService); diff --git a/src/addons/messages/pages/discussion/discussion.page.ts b/src/addons/messages/pages/discussion/discussion.page.ts index a66e6af9a..6717cb529 100644 --- a/src/addons/messages/pages/discussion/discussion.page.ts +++ b/src/addons/messages/pages/discussion/discussion.page.ts @@ -164,9 +164,9 @@ export class AddonMessagesDiscussionPage implements OnInit, OnDestroy, AfterView this.route.queryParams.subscribe(async (params) => { this.loaded = false; - this.conversationId = CoreNavigator.getRouteNumberParam('conversationId', params) || undefined; - this.userId = CoreNavigator.getRouteNumberParam('userId', params) || undefined; - this.showKeyboard = CoreNavigator.getRouteBooleanParam('showKeyboard', params) || false; + this.conversationId = CoreNavigator.getRouteNumberParam('conversationId', { params }) || undefined; + this.userId = CoreNavigator.getRouteNumberParam('userId', { params }) || undefined; + this.showKeyboard = CoreNavigator.getRouteBooleanParam('showKeyboard', { params }) || false; await this.fetchData(); diff --git a/src/addons/messages/pages/discussions-35/discussions.page.ts b/src/addons/messages/pages/discussions-35/discussions.page.ts index 3f8e8370e..34a935772 100644 --- a/src/addons/messages/pages/discussions-35/discussions.page.ts +++ b/src/addons/messages/pages/discussions-35/discussions.page.ts @@ -138,8 +138,8 @@ export class AddonMessagesDiscussions35Page implements OnInit, OnDestroy { */ ngOnInit(): void { this.route.queryParams.subscribe(async (params) => { - const discussionUserId = CoreNavigator.getRouteNumberParam('discussionUserId', params) || - CoreNavigator.getRouteNumberParam('userId', params) || undefined; + const discussionUserId = CoreNavigator.getRouteNumberParam('discussionUserId', { params }) || + CoreNavigator.getRouteNumberParam('userId', { params }) || undefined; if (this.loaded && this.discussionUserId == discussionUserId) { return; diff --git a/src/addons/messages/pages/group-conversations/group-conversations.page.ts b/src/addons/messages/pages/group-conversations/group-conversations.page.ts index 27f6b30f8..2757fc7a2 100644 --- a/src/addons/messages/pages/group-conversations/group-conversations.page.ts +++ b/src/addons/messages/pages/group-conversations/group-conversations.page.ts @@ -273,9 +273,9 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { ngOnInit(): void { this.route.queryParams.subscribe(async (params) => { // Conversation to load. - this.conversationId = CoreNavigator.getRouteNumberParam('conversationId', params) || undefined; + this.conversationId = CoreNavigator.getRouteNumberParam('conversationId', { params }) || undefined; if (!this.conversationId) { - this.discussionUserId = CoreNavigator.getRouteNumberParam('discussionUserId', params) || undefined; + this.discussionUserId = CoreNavigator.getRouteNumberParam('discussionUserId', { params }) || undefined; } if (this.conversationId || this.discussionUserId) { diff --git a/src/addons/messages/services/handlers/user-send-message.ts b/src/addons/messages/services/handlers/user-send-message.ts index d0566d7cd..bfb9f3b79 100644 --- a/src/addons/messages/services/handlers/user-send-message.ts +++ b/src/addons/messages/services/handlers/user-send-message.ts @@ -40,6 +40,13 @@ export class AddonMessagesSendMessageUserHandlerService implements CoreUserProfi return AddonMessages.isPluginEnabled(); } + /** + * @inheritdoc + */ + async isEnabledForCourse(): Promise { + return !!CoreSites.getCurrentSite(); + } + /** * Check if handler is enabled for this user in this context. * @@ -47,14 +54,10 @@ export class AddonMessagesSendMessageUserHandlerService implements CoreUserProfi * @return Promise resolved with true if enabled, resolved with false otherwise. */ async isEnabledForUser(user: CoreUserProfile): Promise { - const currentSite = CoreSites.getCurrentSite(); - - if (!currentSite) { - return false; - } + const currentSite = CoreSites.getCurrentSite()!; // From 3.7 you can send messages to yourself. - return user.id != currentSite.getUserId() || currentSite.isVersionGreaterEqualThan('3.7'); + return user.id != CoreSites.getCurrentSiteUserId() || currentSite.isVersionGreaterEqualThan('3.7'); } /** diff --git a/src/addons/mod/assign/pages/submission-review/submission-review.ts b/src/addons/mod/assign/pages/submission-review/submission-review.ts index eae18746b..2c5e846d7 100644 --- a/src/addons/mod/assign/pages/submission-review/submission-review.ts +++ b/src/addons/mod/assign/pages/submission-review/submission-review.ts @@ -58,7 +58,7 @@ export class AddonModAssignSubmissionReviewPage implements OnInit, CanLeave { this.moduleId = CoreNavigator.getRouteNumberParam('cmId')!; this.courseId = CoreNavigator.getRouteNumberParam('courseId')!; this.submitId = CoreNavigator.getRouteNumberParam('submitId') || 0; - this.blindId = CoreNavigator.getRouteNumberParam('blindId', params); + this.blindId = CoreNavigator.getRouteNumberParam('blindId', { params }); this.fetchSubmission().finally(() => { this.loaded = true; diff --git a/src/core/features/compile/services/compile.ts b/src/core/features/compile/services/compile.ts index e9a5dab79..95e1d523f 100644 --- a/src/core/features/compile/services/compile.ts +++ b/src/core/features/compile/services/compile.ts @@ -117,6 +117,7 @@ import { CoreSitePluginsAssignSubmissionComponent } from '@features/siteplugins/ // Import addon providers. Do not import database module because it causes circular dependencies. import { ADDON_BADGES_SERVICES } from '@addons/badges/badges.module'; import { ADDON_CALENDAR_SERVICES } from '@addons/calendar/calendar.module'; +import { ADDON_COURSECOMPLETION_SERVICES } from '@addons/coursecompletion/coursecompletion.module'; // @todo import { ADDON_COMPETENCY_SERVICES } from '@addons/competency/competency.module'; import { ADDON_MESSAGEOUTPUT_SERVICES } from '@addons/messageoutput/messageoutput.module'; import { ADDON_MESSAGES_SERVICES } from '@addons/messages/messages.module'; @@ -281,6 +282,7 @@ export class CoreCompileProvider { ...extraProviders, ...ADDON_BADGES_SERVICES, ...ADDON_CALENDAR_SERVICES, + ...ADDON_COURSECOMPLETION_SERVICES, // @todo ...ADDON_COMPETENCY_SERVICES, ...ADDON_MESSAGEOUTPUT_SERVICES, ...ADDON_MESSAGES_SERVICES, diff --git a/src/core/features/contentlinks/classes/module-list-handler.ts b/src/core/features/contentlinks/classes/module-list-handler.ts index 6b4f5528c..9d4089b4f 100644 --- a/src/core/features/contentlinks/classes/module-list-handler.ts +++ b/src/core/features/contentlinks/classes/module-list-handler.ts @@ -61,9 +61,8 @@ export class CoreContentLinksModuleListHandler extends CoreContentLinksHandlerBa return [{ action: (siteId): void => { - CoreNavigator.navigateToSitePath('course/list-mod-type', { + CoreNavigator.navigateToSitePath('course/' + params.id + '/list-mod-type', { params: { - courseId: params.id, modName: this.modName, title: this.title || Translate.instant('addon.mod_' + this.modName + '.modulenameplural'), }, diff --git a/src/core/features/course/course-lazy.module.ts b/src/core/features/course/course-lazy.module.ts index a6b626437..fec984b70 100644 --- a/src/core/features/course/course-lazy.module.ts +++ b/src/core/features/course/course-lazy.module.ts @@ -22,18 +22,23 @@ const routes: Routes = [ pathMatch: 'full', }, { - path: 'index', + path: ':courseId', loadChildren: () => import('./pages/index/index.module').then( m => m.CoreCourseIndexPageModule), }, { - path: 'unsupported-module', + path: ':courseId/unsupported-module', loadChildren: () => import('./pages/unsupported-module/unsupported-module.module') .then( m => m.CoreCourseUnsupportedModulePageModule), }, { - path: 'list-mod-type', + path: ':courseId/list-mod-type', loadChildren: () => import('./pages/list-mod-type/list-mod-type.module').then(m => m.CoreCourseListModTypePageModule), }, + { + path: ':courseId/preview', + loadChildren: () => + import('./pages/preview/preview.module').then(m => m.CoreCoursePreviewPageModule), + }, ]; @NgModule({ diff --git a/src/core/features/course/pages/contents/contents.ts b/src/core/features/course/pages/contents/contents.ts index 1a3c4823c..bbc5ac7b0 100644 --- a/src/core/features/course/pages/contents/contents.ts +++ b/src/core/features/course/pages/contents/contents.ts @@ -460,7 +460,10 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy { * Open the course summary */ openCourseSummary(): void { - CoreNavigator.navigateToSitePath('/courses/preview', { params: { course: this.course, avoidOpenCourse: true } }); + CoreNavigator.navigateToSitePath( + '/course/' + this.course.id + '/preview', + { params: { course: this.course, avoidOpenCourse: true } }, + ); } /** diff --git a/src/core/features/course/pages/index/index.module.ts b/src/core/features/course/pages/index/index.module.ts index 5cdcf4b04..b8a598869 100644 --- a/src/core/features/course/pages/index/index.module.ts +++ b/src/core/features/course/pages/index/index.module.ts @@ -17,7 +17,7 @@ import { RouterModule, ROUTES, Routes } from '@angular/router'; import { resolveModuleRoutes } from '@/app/app-routing.module'; import { CoreSharedModule } from '@/core/shared.module'; -import { CoreCourseIndexPage } from './index'; +import { CoreCourseIndexPage } from './index.page'; import { COURSE_INDEX_ROUTES } from './index-routing.module'; function buildRoutes(injector: Injector): Routes { diff --git a/src/core/features/course/pages/index/index.ts b/src/core/features/course/pages/index/index.page.ts similarity index 99% rename from src/core/features/course/pages/index/index.ts rename to src/core/features/course/pages/index/index.page.ts index 3ef58db7d..9a2eae993 100644 --- a/src/core/features/course/pages/index/index.ts +++ b/src/core/features/course/pages/index/index.page.ts @@ -123,7 +123,6 @@ export class CoreCourseIndexPage implements OnInit, OnDestroy { handlers.forEach((handler, index) => { handler.data.page = CoreTextUtils.concatenatePaths(this.currentPagePath, handler.data.page); handler.data.pageParams = handler.data.pageParams || {}; - handler.data.pageParams.courseId = this.course!.id; // Check if this handler should be the first selected tab. if (this.firstTabName && handler.name == this.firstTabName) { diff --git a/src/core/features/course/pages/list-mod-type/list-mod-type.module.ts b/src/core/features/course/pages/list-mod-type/list-mod-type.module.ts index 5070cacda..4102b1136 100644 --- a/src/core/features/course/pages/list-mod-type/list-mod-type.module.ts +++ b/src/core/features/course/pages/list-mod-type/list-mod-type.module.ts @@ -16,7 +16,7 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { CoreSharedModule } from '@/core/shared.module'; -import { CoreCourseListModTypePage } from './list-mod-type'; +import { CoreCourseListModTypePage } from './list-mod-type.page'; import { CoreCourseComponentsModule } from '@features/course/components/components.module'; const routes: Routes = [ diff --git a/src/core/features/course/pages/list-mod-type/list-mod-type.ts b/src/core/features/course/pages/list-mod-type/list-mod-type.page.ts similarity index 100% rename from src/core/features/course/pages/list-mod-type/list-mod-type.ts rename to src/core/features/course/pages/list-mod-type/list-mod-type.page.ts diff --git a/src/core/features/courses/pages/course-preview/course-preview.html b/src/core/features/course/pages/preview/preview.html similarity index 98% rename from src/core/features/courses/pages/course-preview/course-preview.html rename to src/core/features/course/pages/preview/preview.html index 2b6c3bcbf..d035d22a2 100644 --- a/src/core/features/courses/pages/course-preview/course-preview.html +++ b/src/core/features/course/pages/preview/preview.html @@ -13,8 +13,8 @@ -
-
+
+
diff --git a/src/core/features/courses/pages/course-preview/course-preview.module.ts b/src/core/features/course/pages/preview/preview.module.ts similarity index 75% rename from src/core/features/courses/pages/course-preview/course-preview.module.ts rename to src/core/features/course/pages/preview/preview.module.ts index d8e0a8879..168aed845 100644 --- a/src/core/features/courses/pages/course-preview/course-preview.module.ts +++ b/src/core/features/course/pages/preview/preview.module.ts @@ -16,13 +16,12 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { CoreSharedModule } from '@/core/shared.module'; -import { CoreCoursesCoursePreviewPage } from './course-preview'; -import { CoreCoursesComponentsModule } from '../../components/components.module'; +import { CoreCoursePreviewPage } from './preview.page'; const routes: Routes = [ { path: '', - component: CoreCoursesCoursePreviewPage, + component: CoreCoursePreviewPage, }, ]; @@ -30,11 +29,10 @@ const routes: Routes = [ imports: [ RouterModule.forChild(routes), CoreSharedModule, - CoreCoursesComponentsModule, ], declarations: [ - CoreCoursesCoursePreviewPage, + CoreCoursePreviewPage, ], exports: [RouterModule], }) -export class CoreCoursesCoursePreviewPageModule { } +export class CoreCoursePreviewPageModule { } diff --git a/src/core/features/courses/pages/course-preview/course-preview.ts b/src/core/features/course/pages/preview/preview.page.ts similarity index 97% rename from src/core/features/courses/pages/course-preview/course-preview.ts rename to src/core/features/course/pages/preview/preview.page.ts index 0b95eb3bc..ef7569fd2 100644 --- a/src/core/features/courses/pages/course-preview/course-preview.ts +++ b/src/core/features/course/pages/preview/preview.page.ts @@ -15,7 +15,7 @@ import { Component, OnDestroy, NgZone, OnInit } from '@angular/core'; import { ModalController, IonRefresher } from '@ionic/angular'; import { CoreApp } from '@services/app'; -import { CoreEventCourseStatusChanged, CoreEventObserver, CoreEvents } from '@singletons/events'; +import { CoreEventObserver, CoreEvents } from '@singletons/events'; import { CoreSites } from '@services/sites'; import { CoreDomUtils } from '@services/utils/dom'; import { CoreTextUtils } from '@services/utils/text'; @@ -32,18 +32,18 @@ import { CoreCourse, CoreCourseProvider } from '@features/course/services/course import { CoreCourseHelper, CorePrefetchStatusInfo } from '@features/course/services/course-helper'; import { Translate } from '@singletons'; import { CoreConstants } from '@/core/constants'; -import { CoreCoursesSelfEnrolPasswordComponent } from '../../components/self-enrol-password/self-enrol-password'; +import { CoreCoursesSelfEnrolPasswordComponent } from '../../../courses/components/self-enrol-password/self-enrol-password'; import { CoreNavigator } from '@services/navigator'; /** * Page that allows "previewing" a course and enrolling in it if enabled and not enrolled. */ @Component({ - selector: 'page-core-courses-course-preview', - templateUrl: 'course-preview.html', - styleUrls: ['course-preview.scss'], + selector: 'page-core-course-preview', + templateUrl: 'preview.html', + styleUrls: ['preview.scss'], }) -export class CoreCoursesCoursePreviewPage implements OnInit, OnDestroy { +export class CoreCoursePreviewPage implements OnInit, OnDestroy { course?: CoreCourseSearchedData; isEnrolled = false; @@ -84,7 +84,7 @@ export class CoreCoursesCoursePreviewPage implements OnInit, OnDestroy { if (this.downloadCourseEnabled) { // Listen for status change in course. - this.courseStatusObserver = CoreEvents.on(CoreEvents.COURSE_STATUS_CHANGED, (data: CoreEventCourseStatusChanged) => { + this.courseStatusObserver = CoreEvents.on(CoreEvents.COURSE_STATUS_CHANGED, (data) => { if (data.courseId == this.course!.id || data.courseId == CoreCourseProvider.ALL_COURSES_CLEARED) { this.updateCourseStatus(data.status); } diff --git a/src/core/features/courses/pages/course-preview/course-preview.scss b/src/core/features/course/pages/preview/preview.scss similarity index 100% rename from src/core/features/courses/pages/course-preview/course-preview.scss rename to src/core/features/course/pages/preview/preview.scss diff --git a/src/core/features/course/pages/unsupported-module/unsupported-module.module.ts b/src/core/features/course/pages/unsupported-module/unsupported-module.module.ts index 82a9e5afd..596d96650 100644 --- a/src/core/features/course/pages/unsupported-module/unsupported-module.module.ts +++ b/src/core/features/course/pages/unsupported-module/unsupported-module.module.ts @@ -16,7 +16,7 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { CoreSharedModule } from '@/core/shared.module'; -import { CoreCourseUnsupportedModulePage } from './unsupported-module'; +import { CoreCourseUnsupportedModulePage } from './unsupported-module.page'; import { CoreCourseComponentsModule } from '@features/course/components/components.module'; const routes: Routes = [ diff --git a/src/core/features/course/pages/unsupported-module/unsupported-module.ts b/src/core/features/course/pages/unsupported-module/unsupported-module.page.ts similarity index 100% rename from src/core/features/course/pages/unsupported-module/unsupported-module.ts rename to src/core/features/course/pages/unsupported-module/unsupported-module.page.ts diff --git a/src/core/features/course/services/course-helper.ts b/src/core/features/course/services/course-helper.ts index 88d7bbb4b..2fbc9701f 100644 --- a/src/core/features/course/services/course-helper.ts +++ b/src/core/features/course/services/course-helper.ts @@ -1848,7 +1848,7 @@ export class CoreCourseHelperProvider { params = params || {}; Object.assign(params, { course: course }); - await CoreNavigator.navigateToSitePath('course', { siteId, params }); + await CoreNavigator.navigateToSitePath('course/' + course.id, { siteId, params }); } } diff --git a/src/core/features/course/services/handlers/default-format.ts b/src/core/features/course/services/handlers/default-format.ts index 35c02270e..d0e800932 100644 --- a/src/core/features/course/services/handlers/default-format.ts +++ b/src/core/features/course/services/handlers/default-format.ts @@ -177,7 +177,7 @@ export class CoreCourseFormatDefaultHandler implements CoreCourseFormatHandler { Object.assign(params, { course: course }); // Don't return the .push promise, we don't want to display a loading modal during the page transition. - CoreNavigator.navigateToSitePath('course', { params }); + CoreNavigator.navigateToSitePath('course/' + course.id, { params }); } /** diff --git a/src/core/features/course/services/handlers/default-module.ts b/src/core/features/course/services/handlers/default-module.ts index 483c3cb5c..052878766 100644 --- a/src/core/features/course/services/handlers/default-module.ts +++ b/src/core/features/course/services/handlers/default-module.ts @@ -64,9 +64,9 @@ export class CoreCourseModuleDefaultHandler implements CoreCourseModuleHandler { event.stopPropagation(); options = options || {}; - options.params = { module, courseId }; + options.params = { module }; - CoreNavigator.navigateToSitePath('course/unsupported-module', options); + CoreNavigator.navigateToSitePath('course/' + courseId + '/unsupported-module', options); }, }; diff --git a/src/core/features/courses/components/course-list-item/course-list-item.ts b/src/core/features/courses/components/course-list-item/course-list-item.ts index c3d92b871..5f3ef4cb7 100644 --- a/src/core/features/courses/components/course-list-item/course-list-item.ts +++ b/src/core/features/courses/components/course-list-item/course-list-item.ts @@ -94,7 +94,10 @@ export class CoreCoursesCourseListItemComponent implements OnInit { if (this.isEnrolled) { CoreCourseHelper.openCourse(this.course); } else { - CoreNavigator.navigate('courses/preview', { params: { course: this.course } }); + CoreNavigator.navigate( + '/course/' + this.course.id + '/preview', + { params: { course: this.course } }, + ); } } diff --git a/src/core/features/courses/courses-lazy.module.ts b/src/core/features/courses/courses-lazy.module.ts index 09095bed1..0fa55f3fb 100644 --- a/src/core/features/courses/courses-lazy.module.ts +++ b/src/core/features/courses/courses-lazy.module.ts @@ -50,12 +50,6 @@ const routes: Routes = [ import('./pages/my-courses/my-courses.module') .then(m => m.CoreCoursesMyCoursesPageModule), }, - { - path: 'preview', - loadChildren: () => - import('./pages/course-preview/course-preview.module') - .then(m => m.CoreCoursesCoursePreviewPageModule), - }, ]; @NgModule({ diff --git a/src/core/features/courses/services/handlers/enrol-push-click.ts b/src/core/features/courses/services/handlers/enrol-push-click.ts index 0731e2463..c606d3384 100644 --- a/src/core/features/courses/services/handlers/enrol-push-click.ts +++ b/src/core/features/courses/services/handlers/enrol-push-click.ts @@ -59,18 +59,14 @@ export class CoreCoursesEnrolPushClickHandlerService implements CorePushNotifica const params: Params = { course: result.course, }; - let page: string; + let page = 'course/' + courseId; if (notification.contexturl?.indexOf('user/index.php') != -1) { // Open the participants tab. - page = 'course'; - params.selectedTab = 'user_participants'; // @todo: Set this when participants is done. - } else if (result.enrolled) { - // User is still enrolled, open the course. - page = 'course'; - } else { + params.selectedTab = 'participants'; // @todo: Set this when participants is done. + } else if (!result.enrolled) { // User not enrolled anymore, open the preview page. - page = 'courses/preview';; + page += '/preview'; } await CoreNavigator.navigateToSitePath(page, { params, siteId: notification.site }); diff --git a/src/core/features/courses/services/handlers/request-push-click.ts b/src/core/features/courses/services/handlers/request-push-click.ts index e361fc0ee..92fdea4fc 100644 --- a/src/core/features/courses/services/handlers/request-push-click.ts +++ b/src/core/features/courses/services/handlers/request-push-click.ts @@ -75,14 +75,11 @@ export class CoreCoursesRequestPushClickHandlerService implements CorePushNotifi const params: Params = { course: result.course, }; - let page: string; + let page = 'course/' + courseId; - if (result.enrolled) { - // User is still enrolled, open the course. - page = 'course'; - } else { + if (!result.enrolled) { // User not enrolled (shouldn't happen), open the preview page. - page = 'courses/preview'; + page += '/preview'; } await CoreNavigator.navigateToSitePath(page, { params, siteId: notification.site }); diff --git a/src/core/features/grades/grades-lazy.module.ts b/src/core/features/grades/grades-lazy.module.ts index f97dfa538..64d960301 100644 --- a/src/core/features/grades/grades-lazy.module.ts +++ b/src/core/features/grades/grades-lazy.module.ts @@ -21,8 +21,8 @@ import { CoreSharedModule } from '@/core/shared.module'; import { CoreGradesCoursePage } from './pages/course/course.page'; import { CoreGradesCoursePageModule } from './pages/course/course.module'; -import { CoreGradesCoursesPage } from './pages/courses/courses'; -import { CoreGradesGradePage } from './pages/grade/grade'; +import { CoreGradesCoursesPage } from './pages/courses/courses.page'; +import { CoreGradesGradePage } from './pages/grade/grade.page'; const mobileRoutes: Routes = [ { diff --git a/src/core/features/grades/grades.module.ts b/src/core/features/grades/grades.module.ts index 3a3a3d23f..2a9c3dbd7 100644 --- a/src/core/features/grades/grades.module.ts +++ b/src/core/features/grades/grades.module.ts @@ -39,6 +39,10 @@ const routes: Routes = [ path: CoreGradesMainMenuHandlerService.PAGE_NAME, loadChildren: () => import('@features/grades/grades-lazy.module').then(m => m.CoreGradesLazyModule), }, + { + path: 'user-grades/:courseId', + loadChildren: () => import('@features/grades/grades-course-lazy.module').then(m => m.CoreGradesCourseLazyModule), + }, ]; const courseIndexRoutes: Routes = [ diff --git a/src/core/features/grades/pages/course/course.html b/src/core/features/grades/pages/course/course.html index 579fffc6d..9a92c07a0 100644 --- a/src/core/features/grades/pages/course/course.html +++ b/src/core/features/grades/pages/course/course.html @@ -12,7 +12,7 @@ - +
diff --git a/src/core/features/grades/pages/course/course.page.ts b/src/core/features/grades/pages/course/course.page.ts index efeca52fc..540beed21 100644 --- a/src/core/features/grades/pages/course/course.page.ts +++ b/src/core/features/grades/pages/course/course.page.ts @@ -45,9 +45,9 @@ export class CoreGradesCoursePage implements AfterViewInit, OnDestroy { @ViewChild(CoreSplitViewComponent) splitView!: CoreSplitViewComponent; - constructor(route: ActivatedRoute) { - const courseId = parseInt(route.snapshot.params.courseId ?? route.snapshot.queryParams.courseId); - const userId = parseInt(route.snapshot.queryParams.userId ?? CoreSites.getCurrentSiteUserId()); + constructor(protected route: ActivatedRoute) { + const courseId = CoreNavigator.getRouteNumberParam('courseId', { route })!; + const userId = CoreNavigator.getRouteNumberParam('userId', { route }) ?? CoreSites.getCurrentSiteUserId(); const useSplitView = route.snapshot.data.useSplitView ?? true; const outsideGradesTab = route.snapshot.data.outsideGradesTab ?? false; diff --git a/src/core/features/grades/pages/courses/courses.html b/src/core/features/grades/pages/courses/courses.html index c18910932..007a67d44 100644 --- a/src/core/features/grades/pages/courses/courses.html +++ b/src/core/features/grades/pages/courses/courses.html @@ -14,7 +14,7 @@ diff --git a/src/core/features/grades/pages/courses/courses.ts b/src/core/features/grades/pages/courses/courses.page.ts similarity index 100% rename from src/core/features/grades/pages/courses/courses.ts rename to src/core/features/grades/pages/courses/courses.page.ts diff --git a/src/core/features/grades/pages/grade/grade.html b/src/core/features/grades/pages/grade/grade.html index 3e9993a8a..db2e2ee0e 100644 --- a/src/core/features/grades/pages/grade/grade.html +++ b/src/core/features/grades/pages/grade/grade.html @@ -11,7 +11,7 @@ - + ( @@ -167,7 +169,7 @@ export class CoreGradesProvider { ); if (!grades?.usergrades?.[0]) { - throw new Error('Couldn\'t get course grades items'); + throw new CoreError('Couldn\'t get course grades items'); } return grades.usergrades[0].gradeitems; @@ -198,19 +200,19 @@ export class CoreGradesProvider { courseid: courseId, userid: userId, }; - const preSets = { + const preSets: CoreSiteWSPreSets = { cacheKey: this.getCourseGradesCacheKey(courseId, userId), }; if (ignoreCache) { - preSets['getFromCache'] = 0; - preSets['emergencyCache'] = 0; + preSets.getFromCache = false; + preSets.emergencyCache = false; } const table = await site.read('gradereport_user_get_grades_table', params, preSets); if (!table?.tables?.[0]) { - throw new Error('Coudln\'t get course grades table'); + throw new CoreError('Coudln\'t get course grades table'); } return table.tables[0]; @@ -228,7 +230,7 @@ export class CoreGradesProvider { this.logger.debug('Get course grades'); const params: CoreGradesGetOverviewCourseGradesWSParams = {}; - const preSets = { + const preSets: CoreSiteWSPreSets = { cacheKey: this.getCoursesGradesCacheKey(), }; @@ -252,9 +254,10 @@ export class CoreGradesProvider { * @param siteId Site ID (empty for current site). * @return Promise resolved when the data is invalidated. */ - invalidateAllCourseGradesData(courseId: number, siteId?: string): Promise { - return CoreSites.getSite(siteId) - .then((site) => site.invalidateWsCacheForKeyStartingWith(this.getCourseGradesPrefixCacheKey(courseId))); + async invalidateAllCourseGradesData(courseId: number, siteId?: string): Promise { + const site = await CoreSites.getSite(siteId); + + await site.invalidateWsCacheForKeyStartingWith(this.getCourseGradesPrefixCacheKey(courseId)); } /** @@ -265,12 +268,11 @@ export class CoreGradesProvider { * @param siteId Site id (empty for current site). * @return Promise resolved when the data is invalidated. */ - invalidateCourseGradesData(courseId: number, userId?: number, siteId?: string): Promise { - return CoreSites.getSite(siteId).then((site) => { - userId = userId || site.getUserId(); + async invalidateCourseGradesData(courseId: number, userId?: number, siteId?: string): Promise { + const site = await CoreSites.getSite(siteId); + userId = userId || site.getUserId(); - return site.invalidateWsCacheForKey(this.getCourseGradesCacheKey(courseId, userId)); - }); + await site.invalidateWsCacheForKey(this.getCourseGradesCacheKey(courseId, userId)); } /** @@ -279,8 +281,10 @@ export class CoreGradesProvider { * @param siteId Site id (empty for current site). * @return Promise resolved when the data is invalidated. */ - invalidateCoursesGradesData(siteId?: string): Promise { - return CoreSites.getSite(siteId).then((site) => site.invalidateWsCacheForKey(this.getCoursesGradesCacheKey())); + async invalidateCoursesGradesData(siteId?: string): Promise { + const site = await CoreSites.getSite(siteId); + + await site.invalidateWsCacheForKey(this.getCoursesGradesCacheKey()); } /** @@ -292,9 +296,10 @@ export class CoreGradesProvider { * @param siteId Site id (empty for current site). * @return Promise resolved when the data is invalidated. */ - invalidateCourseGradesItemsData(courseId: number, userId: number, groupId?: number, siteId?: string): Promise { - return CoreSites.getSite(siteId) - .then((site) => site.invalidateWsCacheForKey(this.getCourseGradesItemsCacheKey(courseId, userId, groupId))); + async invalidateCourseGradesItemsData(courseId: number, userId: number, groupId?: number, siteId?: string): Promise { + const site = await CoreSites.getSite(siteId); + + await site.invalidateWsCacheForKey(this.getCourseGradesItemsCacheKey(courseId, userId, groupId)); } /** @@ -304,16 +309,17 @@ export class CoreGradesProvider { * @return Resolve with true if plugin is enabled, false otherwise. * @since Moodle 3.2 */ - isCourseGradesEnabled(siteId?: string): Promise { - return CoreSites.getSite(siteId).then((site) => { - if (!site.wsAvailable('gradereport_overview_get_course_grades')) { - return false; - } - // Now check that the configurable mygradesurl is pointing to the gradereport_overview plugin. - const url = site.getStoredConfig('mygradesurl') || ''; + async isCourseGradesEnabled(siteId?: string): Promise { + const site = await CoreSites.getSite(siteId); - return url.indexOf('/grade/report/overview/') !== -1; - }); + if (!site.wsAvailable('gradereport_overview_get_course_grades')) { + return false; + } + + // Now check that the configurable mygradesurl is pointing to the gradereport_overview plugin. + const url = site.getStoredConfig('mygradesurl') || ''; + + return url.indexOf('/grade/report/overview/') !== -1; } /** @@ -323,13 +329,14 @@ export class CoreGradesProvider { * @param siteId Site ID. If not defined, current site. * @return Promise resolved with true if plugin is enabled, rejected or resolved with false otherwise. */ - isPluginEnabledForCourse(courseId: number, siteId?: string): Promise { + async isPluginEnabledForCourse(courseId?: number, siteId?: string): Promise { if (!courseId) { - return Promise.reject(null); + return false; } - return CoreCourses.getUserCourse(courseId, true, siteId) - .then((course) => !(course && typeof course.showgrades != 'undefined' && !course.showgrades)); + const course = await CoreCourses.getUserCourse(courseId, true, siteId); + + return !(course && typeof course.showgrades != 'undefined' && !course.showgrades); } /** @@ -373,9 +380,11 @@ export class CoreGradesProvider { CorePushNotifications.logViewEvent(courseId, name, 'grades', wsName, { userid: userId }); } - const site = await CoreSites.getCurrentSite(); + const site = CoreSites.getCurrentSite(); - await site?.write(wsName, { courseid: courseId, userid: userId }); + const params: CoreGradesGradereportViewGradeReportWSParams = { courseid: courseId, userid: userId }; + + await site?.write(wsName, params); } /** @@ -389,13 +398,13 @@ export class CoreGradesProvider { courseId = CoreSites.getCurrentSiteHomeId(); } - const params = { + const params: CoreGradesGradereportViewGradeReportWSParams = { courseid: courseId, }; CorePushNotifications.logViewListEvent('grades', 'gradereport_overview_view_grade_report', params); - const site = await CoreSites.getCurrentSite(); + const site = CoreSites.getCurrentSite(); await site?.write('gradereport_overview_view_grade_report', params); } @@ -404,6 +413,14 @@ export class CoreGradesProvider { export const CoreGrades = makeSingleton(CoreGradesProvider); +/** + * Params of gradereport_user_view_grade_report and gradereport_overview_view_grade_report WS. + */ +type CoreGradesGradereportViewGradeReportWSParams = { + courseid: number; // Id of the course. + userid?: number; // Id of the user, 0 means current user. +}; + /** * Params of gradereport_user_get_grade_items WS. */ diff --git a/src/core/features/grades/services/handlers/course-option.ts b/src/core/features/grades/services/handlers/course-option.ts index 0621b9a7a..d3954b748 100644 --- a/src/core/features/grades/services/handlers/course-option.ts +++ b/src/core/features/grades/services/handlers/course-option.ts @@ -54,8 +54,8 @@ export class CoreGradesCourseOptionHandlerService implements CoreCourseOptionsHa * * @return Whether or not the handler is enabled on a site level. */ - isEnabled(): Promise { - return Promise.resolve(true); + async isEnabled(): Promise { + return true; } /** diff --git a/src/core/features/grades/services/handlers/mainmenu.ts b/src/core/features/grades/services/handlers/mainmenu.ts index 344cfd6d3..6e7a7b915 100644 --- a/src/core/features/grades/services/handlers/mainmenu.ts +++ b/src/core/features/grades/services/handlers/mainmenu.ts @@ -44,7 +44,7 @@ export class CoreGradesMainMenuHandlerService implements CoreMainMenuHandler { */ getDisplayData(): CoreMainMenuHandlerData { return { - icon: 'stats-chart', + icon: 'fas-chart-bar', title: 'core.grades.grades', page: CoreGradesMainMenuHandlerService.PAGE_NAME, class: 'core-grades-coursesgrades-handler', diff --git a/src/core/features/grades/services/handlers/user.ts b/src/core/features/grades/services/handlers/user.ts index 1d7db05d5..544044018 100644 --- a/src/core/features/grades/services/handlers/user.ts +++ b/src/core/features/grades/services/handlers/user.ts @@ -34,79 +34,41 @@ export class CoreGradesUserHandlerService implements CoreUserProfileHandler { name = 'CoreGrades:viewGrades'; priority = 400; type = CoreUserDelegateService.TYPE_NEW_PAGE; - viewGradesEnabledCache = {}; + cacheEnabled = true; /** - * Clear view grades cache. - * If a courseId and userId are specified, it will only delete the entry for that user and course. - * - * @param courseId Course ID. - * @param userId User ID. + * @inheritdoc */ - clearViewGradesCache(courseId?: number, userId?: number): void { - if (courseId && userId) { - delete this.viewGradesEnabledCache[this.getCacheKey(courseId, userId)]; - } else { - this.viewGradesEnabledCache = {}; - } + async isEnabled(): Promise { + return true; } /** - * Get a cache key to identify a course and a user. - * - * @param courseId Course ID. - * @param userId User ID. - * @return Cache key. + * @inheritdoc */ - protected getCacheKey(courseId: number, userId: number): string { - return courseId + '#' + userId; + async isEnabledForCourse(courseId?: number): Promise { + return CoreUtils.ignoreErrors(CoreGrades.isPluginEnabledForCourse(courseId), false); } /** - * Check if handler is enabled. - * - * @return Always enabled. + * @inheritdoc */ - isEnabled(): Promise { - return Promise.resolve(true); + async isEnabledForUser(user: CoreUserProfile, courseId?: number): Promise { + return CoreUtils.promiseWorks(CoreGrades.getCourseGradesTable(courseId!, user.id)); } /** - * Check if handler is enabled for this user in this context. - * - * @param user User to check. - * @param courseId Course ID. - * @return Promise resolved with true if enabled, resolved with false otherwise. - */ - async isEnabledForUser(user: CoreUserProfile, courseId: number): Promise { - const cacheKey = this.getCacheKey(courseId, user.id); - const cache = this.viewGradesEnabledCache[cacheKey]; - - if (typeof cache != 'undefined') { - return cache; - } - - const enabled = await CoreUtils.ignoreErrors(CoreGrades.isPluginEnabledForCourse(courseId), false); - - this.viewGradesEnabledCache[cacheKey] = enabled; - - return enabled; - } - - /** - * Returns the data needed to render the handler. - * - * @return Data needed to render the handler. + * @inheritdoc */ getDisplayData(): CoreUserProfileHandlerData { return { - icon: 'stats-chart', + icon: 'fas-chart-bar', title: 'core.grades.grades', class: 'core-grades-user-handler', action: (event, user, courseId): void => { event.preventDefault(); event.stopPropagation(); - CoreNavigator.navigateToSitePath(`/grades/${courseId}`, { + CoreNavigator.navigateToSitePath(`/user-grades/${courseId}`, { params: { userId: user.id }, }); }, diff --git a/src/core/features/siteplugins/classes/handlers/user-handler.ts b/src/core/features/siteplugins/classes/handlers/user-handler.ts index 86978a13e..315e198da 100644 --- a/src/core/features/siteplugins/classes/handlers/user-handler.ts +++ b/src/core/features/siteplugins/classes/handlers/user-handler.ts @@ -55,24 +55,12 @@ export class CoreSitePluginsUserProfileHandler extends CoreSitePluginsBaseHandle /** * @inheritdoc */ - async isEnabledForUser( - user: CoreUserProfile, + async isEnabledForCourse( courseId?: number, ): Promise { - // First check if it's enabled for the user. - const enabledForUser = CoreSitePlugins.isHandlerEnabledForUser( - user.id, - this.handlerSchema.restricttocurrentuser, - this.initResult?.restrict, - ); - - if (!enabledForUser) { - return false; - } - courseId = courseId || CoreSites.getCurrentSiteHomeId(); - // Enabled for user, check if it's enabled for the course. + // Check if it's enabled for the course. return CoreSitePlugins.isHandlerEnabledForCourse( courseId, this.handlerSchema.restricttoenrolledcourses, @@ -80,6 +68,19 @@ export class CoreSitePluginsUserProfileHandler extends CoreSitePluginsBaseHandle ); } + /** + * @inheritdoc + */ + async isEnabledForUser( + user: CoreUserProfile, + ): Promise { + return CoreSitePlugins.isHandlerEnabledForUser( + user.id, + this.handlerSchema.restricttocurrentuser, + this.initResult?.restrict, + ); + } + /** * @inheritdoc */ diff --git a/src/core/features/user/pages/participants/participants.ts b/src/core/features/user/pages/participants/participants.page.ts similarity index 97% rename from src/core/features/user/pages/participants/participants.ts rename to src/core/features/user/pages/participants/participants.page.ts index 9757e5053..7c587f5de 100644 --- a/src/core/features/user/pages/participants/participants.ts +++ b/src/core/features/user/pages/participants/participants.page.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { ActivatedRoute, ActivatedRouteSnapshot } from '@angular/router'; +import { ActivatedRouteSnapshot } from '@angular/router'; import { AfterViewInit, Component, OnDestroy, OnInit, ViewChild } from '@angular/core'; import { IonRefresher } from '@ionic/angular'; @@ -43,8 +43,8 @@ export class CoreUserParticipantsPage implements OnInit, AfterViewInit, OnDestro @ViewChild(CoreSplitViewComponent) splitView!: CoreSplitViewComponent; - constructor(route: ActivatedRoute) { - const courseId = parseInt(route.snapshot.queryParams.courseId); + constructor() { + const courseId = CoreNavigator.getRouteNumberParam('courseId')!; this.participants = new CoreUserParticipantsManager(CoreUserParticipantsPage, courseId); } diff --git a/src/core/features/user/services/handlers/profile-mail.ts b/src/core/features/user/services/handlers/profile-mail.ts index 2af4d8f8f..d2c243643 100644 --- a/src/core/features/user/services/handlers/profile-mail.ts +++ b/src/core/features/user/services/handlers/profile-mail.ts @@ -31,35 +31,23 @@ export class CoreUserProfileMailHandlerService implements CoreUserProfileHandler type = CoreUserDelegateService.TYPE_COMMUNICATION; /** - * Check if handler is enabled. - * - * @return Always enabled. + * @inheritdoc */ async isEnabled(): Promise { return true; } /** - * Check if handler is enabled for this user in this context. - * - * @param user User to check. - * @param courseId Course ID. - * @param navOptions Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions. - * @param admOptions Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions. - * @return Promise resolved with true if enabled, resolved with false otherwise. + * @inheritdoc */ - // eslint-disable-next-line @typescript-eslint/no-unused-vars - async isEnabledForUser(user: CoreUserProfile, courseId: number, navOptions?: unknown, admOptions?: unknown): Promise { + async isEnabledForUser(user: CoreUserProfile): Promise { return user.id != CoreSites.getCurrentSiteUserId() && !!user.email; } /** - * Returns the data needed to render the handler. - * - * @return Data needed to render the handler. + * @inheritdoc */ - // eslint-disable-next-line @typescript-eslint/no-unused-vars - getDisplayData(user: CoreUserProfile, courseId: number): CoreUserProfileHandlerData { + getDisplayData(): CoreUserProfileHandlerData { return { icon: 'mail', title: 'core.user.sendemail', diff --git a/src/core/features/user/services/user-delegate.ts b/src/core/features/user/services/user-delegate.ts index 1f2205369..551e22ad9 100644 --- a/src/core/features/user/services/user-delegate.ts +++ b/src/core/features/user/services/user-delegate.ts @@ -18,7 +18,7 @@ import { Subject, BehaviorSubject } from 'rxjs'; import { CoreDelegate, CoreDelegateHandler } from '@classes/delegate'; import { CoreUtils } from '@services/utils/utils'; import { CoreEvents } from '@singletons/events'; -import { CoreUserProfile } from './user'; +import { CoreUserProfile, CoreUserProvider } from './user'; import { makeSingleton } from '@singletons'; import { CoreCourses, CoreCourseUserAdminOrNavOptionIndexed } from '@features/courses/services/courses'; import { CoreSites } from '@services/sites'; @@ -42,21 +42,36 @@ export interface CoreUserProfileHandler extends CoreDelegateHandler { type: string; /** - * Whether or not the handler is enabled for a user. + * If isEnabledForUser Cache should be enabled. + */ + cacheEnabled?: boolean; + + /** + * Whether or not the handler is enabled for a course. * - * @param user User object. * @param courseId Course ID where to show. * @param navOptions Navigation options for the course. * @param admOptions Admin options for the course. * @return Whether or not the handler is enabled for a user. */ - isEnabledForUser( - user: CoreUserProfile, + isEnabledForCourse?( courseId?: number, navOptions?: CoreCourseUserAdminOrNavOptionIndexed, admOptions?: CoreCourseUserAdminOrNavOptionIndexed, ): Promise; + /** + * Whether or not the handler is enabled for a user. + * + * @param user User object. + * @param courseId Course ID where to show. + * @return Whether or not the handler is enabled for a user. + */ + isEnabledForUser?( + user: CoreUserProfile, + courseId?: number, + ): Promise; + /** * Returns the data needed to render the handler. * @@ -156,6 +171,11 @@ export class CoreUserDelegateService extends CoreDelegate> = {}; + protected featurePrefix = 'CoreUserDelegate_'; // Hold the handlers and the observable to notify them for each user. @@ -186,6 +206,14 @@ export class CoreUserDelegateService extends CoreDelegate { + this.clearHandlerCache(); + }); + + CoreEvents.on(CoreUserProvider.PROFILE_REFRESHED, (data) => { + this.clearHandlerCache(data.courseId, data.userId); + }); } /** @@ -267,7 +295,7 @@ export class CoreUserDelegateService extends CoreDelegate { + if (handler.isEnabledForCourse) { + const enabledOnCourse = await handler.isEnabledForCourse(courseId, navOptions, admOptions); + + if (!enabledOnCourse) { + // If is not enabled in the course, is not enabled for the user. + // Do not cache if this is false. + return false; + } + } + + if (!handler.cacheEnabled) { + if (!handler.isEnabledForUser) { + // True by default. + return true; + } + + return handler.isEnabledForUser(user, courseId); + } + + if (typeof this.enabledForUserCache[handler.name] == 'undefined') { + this.enabledForUserCache[handler.name] = {}; + } + + const cacheKey = this.getCacheKey(courseId, user.id); + const cache = this.enabledForUserCache[handler.name][cacheKey]; + + if (typeof cache != 'undefined') { + return cache; + } + + let enabled = true; // Default value. + if (handler.isEnabledForUser) { + enabled = await handler.isEnabledForUser(user, courseId); + } + + this.enabledForUserCache[handler.name][cacheKey] = enabled; + + return enabled; + } + + /** + * Clear handler enabled for user cache. + * If a courseId and userId are specified, it will only delete the entry for that user and course. + * + * @param courseId Course ID. + * @param userId User ID. + */ + protected clearHandlerCache(courseId?: number, userId?: number): void { + if (courseId && userId) { + Object.keys(this.enabledHandlers).forEach((name) => { + delete this.enabledForUserCache[name][this.getCacheKey(courseId, userId)]; + }); + } else { + this.enabledForUserCache = {}; + } + } + + /** + * Get a cache key to identify a course and a user. + * + * @param courseId Course ID. + * @param userId User ID. + * @return Cache key. + */ + protected getCacheKey(courseId = 0, userId = 0): string { + return courseId + '#' + userId; + } + } export const CoreUserDelegate = makeSingleton(CoreUserDelegateService); diff --git a/src/core/features/user/services/user.ts b/src/core/features/user/services/user.ts index 41a4cd95b..1ba500218 100644 --- a/src/core/features/user/services/user.ts +++ b/src/core/features/user/services/user.ts @@ -820,9 +820,22 @@ export class CoreUserProvider { } } - export const CoreUser = makeSingleton(CoreUserProvider); +declare module '@singletons/events' { + + /** + * Augment CoreEventsData interface with events specific to this service. + * + * @see https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation + */ + export interface CoreEventsData { + [CoreUserProvider.PROFILE_REFRESHED]: CoreUserProfileRefreshedData; + [CoreUserProvider.PROFILE_PICTURE_UPDATED]: CoreUserProfilePictureUpdatedData; + } + +} + /** * Data passed to PROFILE_REFRESHED event. */ diff --git a/src/core/features/user/user-course-lazy.module.ts b/src/core/features/user/user-course-lazy.module.ts index 7602ecacd..e08c9408f 100644 --- a/src/core/features/user/user-course-lazy.module.ts +++ b/src/core/features/user/user-course-lazy.module.ts @@ -18,7 +18,7 @@ import { RouterModule, Routes } from '@angular/router'; import { CoreSharedModule } from '@/core/shared.module'; import { CoreSearchComponentsModule } from '@features/search/components/components.module'; -import { CoreUserParticipantsPage } from './pages/participants/participants'; +import { CoreUserParticipantsPage } from './pages/participants/participants.page'; const routes: Routes = [ { diff --git a/src/core/services/navigator.ts b/src/core/services/navigator.ts index ab711adaa..bd223c89d 100644 --- a/src/core/services/navigator.ts +++ b/src/core/services/navigator.ts @@ -52,10 +52,11 @@ export type CoreNavigationOptions = { }; /** - * Options for CoreNavigatorService#getCurrentRoute method. + * Route options to get route or params values. */ -type GetCurrentRouteOptions = Partial<{ - parentRoute: ActivatedRoute; +export type CoreNavigatorCurrentRouteOptions = Partial<{ + params: Params; // Params to get the value from. + route: ActivatedRoute; // Current Route. pageComponent: unknown; }>; @@ -246,8 +247,8 @@ export class CoreNavigatorService { /** * Iterately get the params checking parent routes. * - * @param route Current route. * @param name Name of the parameter. + * @param route Current route. * @return Value of the parameter, undefined if not found. */ protected getRouteSnapshotParam(name: string, route?: ActivatedRoute): T | undefined { @@ -270,18 +271,21 @@ export class CoreNavigatorService { * unless there's a new navigation to the page. * * @param name Name of the parameter. - * @param params Optional params to get the value from. If missing, it will autodetect. + * @param routeOptions Optional routeOptions to get the params or route value from. If missing, it will autodetect. * @return Value of the parameter, undefined if not found. */ - getRouteParam(name: string, params?: Params): T | undefined { + getRouteParam(name: string, routeOptions: CoreNavigatorCurrentRouteOptions = {}): T | undefined { let value: any; - if (!params) { - const route = this.getCurrentRoute(); + if (!routeOptions.params) { + let route = this.getCurrentRoute(); + if (!route?.snapshot && routeOptions.route) { + route = routeOptions.route; + } value = this.getRouteSnapshotParam(name, route); } else { - value = params[name]; + value = routeOptions.params[name]; } if (typeof value == 'undefined') { @@ -309,11 +313,11 @@ export class CoreNavigatorService { * Angular router automatically converts numbers to string, this function automatically converts it back to number. * * @param name Name of the parameter. - * @param params Optional params to get the value from. If missing, it will autodetect. + * @param routeOptions Optional routeOptions to get the params or route value from. If missing, it will autodetect. * @return Value of the parameter, undefined if not found. */ - getRouteNumberParam(name: string, params?: Params): number | undefined { - const value = this.getRouteParam(name, params); + getRouteNumberParam(name: string, routeOptions: CoreNavigatorCurrentRouteOptions = {}): number | undefined { + const value = this.getRouteParam(name, routeOptions); return value !== undefined ? Number(value) : value; } @@ -323,13 +327,25 @@ export class CoreNavigatorService { * Angular router automatically converts booleans to string, this function automatically converts it back to boolean. * * @param name Name of the parameter. - * @param params Optional params to get the value from. If missing, it will autodetect. + * @param routeOptions Optional routeOptions to get the params or route value from. If missing, it will autodetect. * @return Value of the parameter, undefined if not found. */ - getRouteBooleanParam(name: string, params?: Params): boolean | undefined { - const value = this.getRouteParam(name, params); + getRouteBooleanParam(name: string, routeOptions: CoreNavigatorCurrentRouteOptions = {}): boolean | undefined { + const value = this.getRouteParam(name, routeOptions); - return value !== undefined ? Boolean(value) : value; + if (typeof value == 'undefined') { + return value; + } + + if (CoreUtils.isTrueOrOne(value)) { + return true; + } + + if (CoreUtils.isFalseOrZero(value)) { + return false; + } + + return Boolean(value); } /** @@ -345,25 +361,25 @@ export class CoreNavigatorService { * Get current activated route. * * @param options - * - parent: Parent route, if this isn't provided the current active route will be used. + * - route: Parent route, if this isn't provided the current active route will be used. * - pageComponent: Page component of the route to find, if this isn't provided the deepest route in the hierarchy * will be returned. * @return Current activated route. */ getCurrentRoute(): ActivatedRoute; - getCurrentRoute(options: GetCurrentRouteOptions): ActivatedRoute | null; - getCurrentRoute({ parentRoute, pageComponent }: GetCurrentRouteOptions = {}): ActivatedRoute | null { - parentRoute = parentRoute ?? Router.routerState.root; + getCurrentRoute(options: CoreNavigatorCurrentRouteOptions): ActivatedRoute | null; + getCurrentRoute({ route, pageComponent }: CoreNavigatorCurrentRouteOptions = {}): ActivatedRoute | null { + route = route ?? Router.routerState.root; - if (pageComponent && parentRoute.component === pageComponent) { - return parentRoute; + if (pageComponent && route.component === pageComponent) { + return route; } - if (parentRoute.firstChild) { - return this.getCurrentRoute({ parentRoute: parentRoute.firstChild, pageComponent }); + if (route.firstChild) { + return this.getCurrentRoute({ route: route.firstChild, pageComponent }); } - return pageComponent ? null : parentRoute; + return pageComponent ? null : route; } /** diff --git a/src/theme/theme.base.scss b/src/theme/theme.base.scss index bf6558671..34ec0ce08 100644 --- a/src/theme/theme.base.scss +++ b/src/theme/theme.base.scss @@ -309,7 +309,8 @@ img[alt] { // Activity modules .core-module-icon { --size: 24px; - width: auto; + width: var(--size); + height: var(--size); max-width: var(--size); max-height: var(--size); }