diff --git a/src/addons/block/learningplans/services/block-handler.ts b/src/addons/block/learningplans/services/block-handler.ts index 316884f45..ca4caa9e3 100644 --- a/src/addons/block/learningplans/services/block-handler.ts +++ b/src/addons/block/learningplans/services/block-handler.ts @@ -17,7 +17,7 @@ import { CoreBlockHandlerData } from '@features/block/services/block-delegate'; import { CoreBlockOnlyTitleComponent } from '@features/block/components/only-title-block/only-title-block'; import { CoreBlockBaseHandler } from '@features/block/classes/base-block-handler'; import { makeSingleton } from '@singletons'; -import { ADDON_COMPETENCY_MAIN_PAGE_NAME } from '@addons/competency/competency.module'; +import { ADDON_COMPETENCY_LEARNING_PLANS_PAGE } from '@addons/competency/competency.module'; /** * Block handler. @@ -38,7 +38,7 @@ export class AddonBlockLearningPlansHandlerService extends CoreBlockBaseHandler title: 'addon.block_learningplans.pluginname', class: 'addon-block-learning-plans', component: CoreBlockOnlyTitleComponent, - link: ADDON_COMPETENCY_MAIN_PAGE_NAME, + link: ADDON_COMPETENCY_LEARNING_PLANS_PAGE, navOptions: { preferCurrentTab: false, }, diff --git a/src/addons/competency/classes/competency-course-competencies-source.ts b/src/addons/competency/classes/competency-course-competencies-source.ts new file mode 100644 index 000000000..363514900 --- /dev/null +++ b/src/addons/competency/classes/competency-course-competencies-source.ts @@ -0,0 +1,97 @@ +// (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 { CoreRoutedItemsManagerSource } from '@classes/items-management/routed-items-manager-source'; +import { CoreUserProfile } from '@features/user/services/user'; +import { CoreUtils } from '@services/utils/utils'; +import { + AddonCompetency, + AddonCompetencyDataForCourseCompetenciesPageCompetency, + AddonCompetencyDataForCourseCompetenciesPageWSResponse, +} from '../services/competency'; +import { AddonCompetencyHelper } from '../services/competency-helper'; + +/** + * Provides a collection of course competencies. + */ +export class AddonCompetencyCourseCompetenciesSource + extends CoreRoutedItemsManagerSource { + + /** + * @inheritdoc + */ + static getSourceId(courseId: number, userId?: number): string { + return `${courseId}-${userId || 'current-user'}`; + } + + readonly COURSE_ID: number; + readonly USER_ID?: number; + + courseCompetencies?: AddonCompetencyDataForCourseCompetenciesPageWSResponse; + user?: CoreUserProfile; + + constructor(courseId: number, userId?: number) { + super(); + + this.COURSE_ID = courseId; + this.USER_ID = userId; + } + + /** + * @inheritdoc + */ + getItemPath(competency: AddonCompetencyDataForCourseCompetenciesPageCompetency): string { + return String(competency.competency.id); + } + + /** + * @inheritdoc + */ + async load(): Promise { + if (this.dirty || !this.courseCompetencies) { + await this.loadCourseCompetencies(); + } + + await super.load(); + } + + /** + * Invalidate course cache. + */ + async invalidateCache(): Promise { + await CoreUtils.ignoreErrors(AddonCompetency.invalidateCourseCompetencies(this.COURSE_ID, this.USER_ID)); + } + + /** + * @inheritdoc + */ + protected async loadPageItems(): Promise<{ items: AddonCompetencyDataForCourseCompetenciesPageCompetency[] }> { + if (!this.courseCompetencies) { + throw new Error('Can\'t load competencies without course data'); + } + + return { items: this.courseCompetencies.competencies }; + } + + /** + * Load competencies. + */ + private async loadCourseCompetencies(): Promise { + [this.courseCompetencies, this.user] = await Promise.all([ + AddonCompetency.getCourseCompetencies(this.COURSE_ID, this.USER_ID), + AddonCompetencyHelper.getProfile(this.USER_ID), + ]); + } + +} diff --git a/src/addons/competency/classes/competency-plan-competencies-source.ts b/src/addons/competency/classes/competency-plan-competencies-source.ts new file mode 100644 index 000000000..a674f4c1e --- /dev/null +++ b/src/addons/competency/classes/competency-plan-competencies-source.ts @@ -0,0 +1,88 @@ +// (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 { CoreRoutedItemsManagerSource } from '@classes/items-management/routed-items-manager-source'; +import { CoreUserProfile } from '@features/user/services/user'; +import { CoreUtils } from '@services/utils/utils'; +import { + AddonCompetency, + AddonCompetencyDataForPlanPageCompetency, + AddonCompetencyDataForPlanPageWSResponse, +} from '../services/competency'; +import { AddonCompetencyHelper } from '../services/competency-helper'; + +/** + * Provides a collection of plan competencies. + */ +export class AddonCompetencyPlanCompetenciesSource extends CoreRoutedItemsManagerSource { + + readonly PLAN_ID: number; + + plan?: AddonCompetencyDataForPlanPageWSResponse; + user?: CoreUserProfile; + + constructor(planId: number) { + super(); + + this.PLAN_ID = planId; + } + + /** + * @inheritdoc + */ + getItemPath(competency: AddonCompetencyDataForPlanPageCompetency): string { + return String(competency.competency.id); + } + + /** + * @inheritdoc + */ + async load(): Promise { + if (this.dirty || !this.plan) { + await this.loadLearningPlan(); + } + + await super.load(); + } + + /** + * Invalidate plan cache. + */ + async invalidateCache(): Promise { + await CoreUtils.ignoreErrors(AddonCompetency.invalidateLearningPlan(this.PLAN_ID)); + } + + /** + * @inheritdoc + */ + protected async loadPageItems(): Promise<{ items: AddonCompetencyDataForPlanPageCompetency[] }> { + if (!this.plan) { + throw new Error('Can\'t load competencies without plan!'); + } + + return { items: this.plan.competencies }; + } + + /** + * Load learning plan. + */ + private async loadLearningPlan(): Promise { + this.plan = await AddonCompetency.getLearningPlan(this.PLAN_ID); + this.plan.plan.statusname = AddonCompetencyHelper.getPlanStatusName(this.plan.plan.status); + + // Get the user profile image. + this.user = await AddonCompetencyHelper.getProfile(this.plan.plan.userid); + } + +} diff --git a/src/addons/competency/classes/competency-plans-source.ts b/src/addons/competency/classes/competency-plans-source.ts new file mode 100644 index 000000000..471db5ba5 --- /dev/null +++ b/src/addons/competency/classes/competency-plans-source.ts @@ -0,0 +1,97 @@ +// (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 { Params } from '@angular/router'; +import { CoreRoutedItemsManagerSource } from '@classes/items-management/routed-items-manager-source'; +import { ADDON_COMPETENCY_COMPETENCIES_PAGE } from '../competency.module'; +import { AddonCompetency, AddonCompetencyPlan, AddonCompetencyProvider } from '../services/competency'; +import { AddonCompetencyHelper } from '../services/competency-helper'; + +/** + * Provides a collection of learning plans. + */ +export class AddonCompetencyPlansSource extends CoreRoutedItemsManagerSource { + + /** + * @inheritdoc + */ + static getSourceId(userId?: number): string { + return userId ? String(userId) : 'current-user'; + } + + readonly USER_ID?: number; + + constructor(userId?: number) { + super(); + + this.USER_ID = userId; + } + + /** + * @inheritdoc + */ + getItemPath(plan: AddonCompetencyPlanFormatted): string { + return `${plan.id}/${ADDON_COMPETENCY_COMPETENCIES_PAGE}`; + } + + /** + * @inheritdoc + */ + getItemQueryParams(): Params { + if (this.USER_ID) { + return { userId: this.USER_ID }; + } + + return {}; + } + + /** + * Invalidate learning plans cache. + */ + async invalidateCache(): Promise { + await AddonCompetency.invalidateLearningPlans(this.USER_ID); + } + + /** + * @inheritdoc + */ + protected async loadPageItems(): Promise<{ items: AddonCompetencyPlanFormatted[] }> { + const plans = await AddonCompetency.getLearningPlans(this.USER_ID); + + plans.forEach((plan: AddonCompetencyPlanFormatted) => { + plan.statusname = AddonCompetencyHelper.getPlanStatusName(plan.status); + switch (plan.status) { + case AddonCompetencyProvider.STATUS_ACTIVE: + plan.statuscolor = 'success'; + break; + case AddonCompetencyProvider.STATUS_COMPLETE: + plan.statuscolor = 'danger'; + break; + default: + plan.statuscolor = 'warning'; + break; + } + }); + + return { items: plans }; + } + +} + +/** + * Competency plan with some calculated data. + */ +export type AddonCompetencyPlanFormatted = AddonCompetencyPlan & { + statuscolor?: string; // Calculated in the app. Color of the plan's status. +}; diff --git a/src/addons/competency/competency-course-lazy.module.ts b/src/addons/competency/competency-course-contents-lazy.module.ts similarity index 95% rename from src/addons/competency/competency-course-lazy.module.ts rename to src/addons/competency/competency-course-contents-lazy.module.ts index 860401684..f132f614b 100644 --- a/src/addons/competency/competency-course-lazy.module.ts +++ b/src/addons/competency/competency-course-contents-lazy.module.ts @@ -15,8 +15,8 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; -import { AddonCompetencyCourseCompetenciesPageModule } from './pages/coursecompetencies/coursecompetencies.module'; import { AddonCompetencyCourseCompetenciesPage } from './pages/coursecompetencies/coursecompetencies.page'; +import { AddonCompetencyCourseCompetenciesPageModule } from './pages/coursecompetencies/coursecompetencies.module'; const routes: Routes = [ { @@ -31,4 +31,4 @@ const routes: Routes = [ AddonCompetencyCourseCompetenciesPageModule, ], }) -export class AddonCompetencyCourseLazyModule {} +export class AddonCompetencyCourseContentsLazyModule {} diff --git a/src/addons/competency/competency-course-details-lazy.module.ts b/src/addons/competency/competency-course-details-lazy.module.ts new file mode 100644 index 000000000..4d9bf9ea7 --- /dev/null +++ b/src/addons/competency/competency-course-details-lazy.module.ts @@ -0,0 +1,72 @@ +// (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 { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; + +import { AddonCompetencyCompetencyPage } from './pages/competency/competency.page'; +import { AddonCompetencyCompetencySummaryPage } from './pages/competencysummary/competencysummary.page'; +import { ADDON_COMPETENCY_SUMMARY_PAGE } from './competency.module'; +import { AddonCompetencyCompetencyPageModule } from './pages/competency/competency.module'; +import { AddonCompetencyCompetencySummaryPageModule } from './pages/competencysummary/competencysummary.module'; +import { AddonCompetencyCourseCompetenciesPage } from './pages/coursecompetencies/coursecompetencies.page'; +import { AddonCompetencyCourseCompetenciesPageModule } from './pages/coursecompetencies/coursecompetencies.module'; +import { AddonCompetencyCompetenciesPage } from './pages/competencies/competencies.page'; +import { conditionalRoutes } from '@/app/app-routing.module'; +import { CoreScreen } from '@services/screen'; +import { AddonCompetencyCompetenciesPageModule } from './pages/competencies/competencies.module'; + +const mobileRoutes: Routes = [ + { + path: '', + component: AddonCompetencyCourseCompetenciesPage, + }, + { + path: ':competencyId', + component: AddonCompetencyCompetencyPage, + }, +]; + +const tabletRoutes: Routes = [ + { + path: '', + component: AddonCompetencyCompetenciesPage, + children: [ + { + path: ':competencyId', + component: AddonCompetencyCompetencyPage, + }, + ], + }, +]; + +const routes: Routes = [ + ...conditionalRoutes(mobileRoutes, () => CoreScreen.isMobile), + ...conditionalRoutes(tabletRoutes, () => CoreScreen.isTablet), + { + path: `:competencyId/${ADDON_COMPETENCY_SUMMARY_PAGE}`, + component: AddonCompetencyCompetencySummaryPage, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + AddonCompetencyCourseCompetenciesPageModule, + AddonCompetencyCompetenciesPageModule, + AddonCompetencyCompetencyPageModule, + AddonCompetencyCompetencySummaryPageModule, + ], +}) +export class AddonCompetencyCourseDetailsLazyModule {} diff --git a/src/addons/competency/competency-lazy.module.ts b/src/addons/competency/competency-learning-plans-lazy.module.ts similarity index 67% rename from src/addons/competency/competency-lazy.module.ts rename to src/addons/competency/competency-learning-plans-lazy.module.ts index 09dce93a7..79dcab799 100644 --- a/src/addons/competency/competency-lazy.module.ts +++ b/src/addons/competency/competency-learning-plans-lazy.module.ts @@ -20,11 +20,13 @@ import { CoreScreen } from '@services/screen'; import { CoreSharedModule } from '@/core/shared.module'; import { AddonCompetencyPlanPage } from './pages/plan/plan'; import { AddonCompetencyPlanListPage } from './pages/planlist/planlist'; -import { AddonCompetencyCompetenciesPage } from './pages/competencies/competencies'; -import { AddonCompetencyCompetencyPage } from './pages/competency/competency'; -import { AddonCompetencyCompetencySummaryPage } from './pages/competencysummary/competencysummary'; -import { AddonCompetencyCourseCompetenciesPage } from './pages/coursecompetencies/coursecompetencies.page'; -import { AddonCompetencyCourseCompetenciesPageModule } from './pages/coursecompetencies/coursecompetencies.module'; +import { AddonCompetencyCompetencyPage } from './pages/competency/competency.page'; +import { AddonCompetencyCompetencySummaryPage } from './pages/competencysummary/competencysummary.page'; +import { ADDON_COMPETENCY_COMPETENCIES_PAGE, ADDON_COMPETENCY_SUMMARY_PAGE } from './competency.module'; +import { AddonCompetencyCompetencyPageModule } from './pages/competency/competency.module'; +import { AddonCompetencyCompetencySummaryPageModule } from './pages/competencysummary/competencysummary.module'; +import { AddonCompetencyCompetenciesPage } from './pages/competencies/competencies.page'; +import { AddonCompetencyCompetenciesPageModule } from './pages/competencies/competencies.module'; const mobileRoutes: Routes = [ { @@ -33,34 +35,28 @@ const mobileRoutes: Routes = [ component: AddonCompetencyPlanListPage, }, { - path: 'competencies', - component: AddonCompetencyCompetenciesPage, - }, - { - path: 'competencies/:competencyId', - component: AddonCompetencyCompetencyPage, - }, - { - path: 'course/:courseId', - component: AddonCompetencyCourseCompetenciesPage, - }, - { - path: 'summary/:competencyId', - component: AddonCompetencyCompetencySummaryPage, - }, - { - path: ':planId', + path: `:planId/${ADDON_COMPETENCY_COMPETENCIES_PAGE}`, component: AddonCompetencyPlanPage, }, + { + path: `:planId/${ADDON_COMPETENCY_COMPETENCIES_PAGE}/:competencyId`, + component: AddonCompetencyCompetencyPage, + }, ]; const tabletRoutes: Routes = [ { - path: 'summary/:competencyId', - component: AddonCompetencyCompetencySummaryPage, + path: '', + component: AddonCompetencyPlanListPage, + children: [ + { + path: `:planId/${ADDON_COMPETENCY_COMPETENCIES_PAGE}`, + component: AddonCompetencyPlanPage, + }, + ], }, { - path: 'competencies', + path: `:planId/${ADDON_COMPETENCY_COMPETENCIES_PAGE}`, component: AddonCompetencyCompetenciesPage, children: [ { @@ -69,40 +65,28 @@ const tabletRoutes: Routes = [ }, ], }, - { - path: 'course/:courseId', - component: AddonCompetencyCourseCompetenciesPage, - }, - { - path: '', - component: AddonCompetencyPlanListPage, - children: [ - { - path: ':planId', - component: AddonCompetencyPlanPage, - }, - - ], - }, ]; const routes: Routes = [ ...conditionalRoutes(mobileRoutes, () => CoreScreen.isMobile), ...conditionalRoutes(tabletRoutes, () => CoreScreen.isTablet), + { + path: `:planId/${ADDON_COMPETENCY_COMPETENCIES_PAGE}/:competencyId/${ADDON_COMPETENCY_SUMMARY_PAGE}`, + component: AddonCompetencyCompetencySummaryPage, + }, ]; @NgModule({ imports: [ RouterModule.forChild(routes), CoreSharedModule, - AddonCompetencyCourseCompetenciesPageModule, + AddonCompetencyCompetenciesPageModule, + AddonCompetencyCompetencyPageModule, + AddonCompetencyCompetencySummaryPageModule, ], declarations: [ AddonCompetencyPlanPage, AddonCompetencyPlanListPage, - AddonCompetencyCompetenciesPage, - AddonCompetencyCompetencyPage, - AddonCompetencyCompetencySummaryPage, ], }) -export class AddonCompetencyLazyModule {} +export class AddonCompetencyLearningPlansLazyModule {} diff --git a/src/addons/competency/competency.module.ts b/src/addons/competency/competency.module.ts index a20438615..b55e996c8 100644 --- a/src/addons/competency/competency.module.ts +++ b/src/addons/competency/competency.module.ts @@ -30,6 +30,8 @@ import { Routes } from '@angular/router'; import { CoreMainMenuRoutingModule } from '@features/mainmenu/mainmenu-routing.module'; import { CoreMainMenuTabRoutingModule } from '@features/mainmenu/mainmenu-tab-routing.module'; import { CoreCourseIndexRoutingModule } from '@features/course/pages/index/index-routing.module'; +import { COURSE_PAGE_NAME } from '@features/course/course.module'; +import { PARTICIPANTS_PAGE_NAME } from '@features/user/user.module'; // List of providers (without handlers). export const ADDON_COMPETENCY_SERVICES: Type[] = [ @@ -37,19 +39,29 @@ export const ADDON_COMPETENCY_SERVICES: Type[] = [ AddonCompetencyHelperProvider, ]; -export const ADDON_COMPETENCY_MAIN_PAGE_NAME = 'competency'; +export const ADDON_COMPETENCY_LEARNING_PLANS_PAGE = 'learning-plans'; +export const ADDON_COMPETENCY_COMPETENCIES_PAGE = 'competencies'; +export const ADDON_COMPETENCY_SUMMARY_PAGE = 'summary'; const mainMenuChildrenRoutes: Routes = [ { - path: ADDON_COMPETENCY_MAIN_PAGE_NAME, - loadChildren: () => import('./competency-lazy.module').then(m => m.AddonCompetencyLazyModule), + path: ADDON_COMPETENCY_LEARNING_PLANS_PAGE, + loadChildren: () => import('./competency-learning-plans-lazy.module').then(m => m.AddonCompetencyLearningPlansLazyModule), + }, + { + path: `${COURSE_PAGE_NAME}/:courseId/${ADDON_COMPETENCY_COMPETENCIES_PAGE}`, + loadChildren: () => import('./competency-course-details-lazy.module').then(m => m.AddonCompetencyCourseDetailsLazyModule), + }, + { + path: `${COURSE_PAGE_NAME}/:courseId/${PARTICIPANTS_PAGE_NAME}/:userId/${ADDON_COMPETENCY_COMPETENCIES_PAGE}`, + loadChildren: () => import('./competency-course-details-lazy.module').then(m => m.AddonCompetencyCourseDetailsLazyModule), }, ]; const courseIndexRoutes: Routes = [ { - path: ADDON_COMPETENCY_MAIN_PAGE_NAME, - loadChildren: () => import('@addons/competency/competency-course-lazy.module').then(m => m.AddonCompetencyCourseLazyModule), + path: ADDON_COMPETENCY_COMPETENCIES_PAGE, + loadChildren: () => import('./competency-course-contents-lazy.module').then(m => m.AddonCompetencyCourseContentsLazyModule), }, ]; diff --git a/src/addons/competency/pages/competencies/competencies.module.ts b/src/addons/competency/pages/competencies/competencies.module.ts new file mode 100644 index 000000000..107a73c21 --- /dev/null +++ b/src/addons/competency/pages/competencies/competencies.module.ts @@ -0,0 +1,28 @@ +// (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 { NgModule } from '@angular/core'; + +import { CoreSharedModule } from '@/core/shared.module'; +import { AddonCompetencyCompetenciesPage } from './competencies.page'; + +@NgModule({ + imports: [ + CoreSharedModule, + ], + declarations: [ + AddonCompetencyCompetenciesPage, + ], +}) +export class AddonCompetencyCompetenciesPageModule {} diff --git a/src/addons/competency/pages/competencies/competencies.page.ts b/src/addons/competency/pages/competencies/competencies.page.ts new file mode 100644 index 000000000..0841df662 --- /dev/null +++ b/src/addons/competency/pages/competencies/competencies.page.ts @@ -0,0 +1,125 @@ +// (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 { AfterViewInit, Component, OnDestroy, ViewChild } from '@angular/core'; +import { IonRefresher } from '@ionic/angular'; +import { CoreDomUtils } from '@services/utils/dom'; +import { CoreSplitViewComponent } from '@components/split-view/split-view'; +import { + AddonCompetencyDataForPlanPageCompetency, + AddonCompetencyDataForCourseCompetenciesPageCompetency, +} from '../../services/competency'; +import { Translate } from '@singletons'; +import { CoreNavigator } from '@services/navigator'; +import { CoreError } from '@classes/errors/error'; +import { AddonCompetencyPlanCompetenciesSource } from '@addons/competency/classes/competency-plan-competencies-source'; +import { AddonCompetencyCourseCompetenciesSource } from '@addons/competency/classes/competency-course-competencies-source'; +import { CoreListItemsManager } from '@classes/items-management/list-items-manager'; +import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker'; + +/** + * Page that displays the list of competencies of a learning plan. + */ +@Component({ + selector: 'page-addon-competency-competencies', + templateUrl: 'competencies.html', +}) +export class AddonCompetencyCompetenciesPage implements AfterViewInit, OnDestroy { + + @ViewChild(CoreSplitViewComponent) splitView!: CoreSplitViewComponent; + + competencies: CoreListItemsManager< + AddonCompetencyDataForPlanPageCompetency | AddonCompetencyDataForCourseCompetenciesPageCompetency, + AddonCompetencyPlanCompetenciesSource | AddonCompetencyCourseCompetenciesSource + >; + + title = ''; + + constructor() { + const planId = CoreNavigator.getRouteNumberParam('planId'); + + if (!planId) { + const courseId = CoreNavigator.getRequiredRouteNumberParam('courseId'); + const userId = CoreNavigator.getRouteNumberParam('userId'); + const source = CoreRoutedItemsManagerSourcesTracker.getOrCreateSource( + AddonCompetencyCourseCompetenciesSource, + [courseId, userId], + ); + + this.competencies = new CoreListItemsManager(source, AddonCompetencyCompetenciesPage); + + return; + } + + const source = CoreRoutedItemsManagerSourcesTracker.getOrCreateSource(AddonCompetencyPlanCompetenciesSource, [planId]); + + this.competencies = new CoreListItemsManager(source, AddonCompetencyCompetenciesPage); + } + + /** + * @inheritdoc + */ + async ngAfterViewInit(): Promise { + await this.fetchCompetencies(); + + this.competencies.start(this.splitView); + } + + /** + * Fetches the competencies and updates the view. + * + * @return Promise resolved when done. + */ + protected async fetchCompetencies(): Promise { + try { + const source = this.competencies.getSource(); + + await this.competencies.load(); + + if (source instanceof AddonCompetencyPlanCompetenciesSource) { + if (!source.plan || source.plan && source.plan.competencycount <= 0) { + throw new CoreError(Translate.instant('addon.competency.errornocompetenciesfound')); + } + + this.title = source.plan.plan.name; + } else { + this.title = Translate.instant('addon.competency.coursecompetencies'); + } + } catch (error) { + CoreDomUtils.showErrorModalDefault(error, 'Error getting competencies data.'); + } + } + + /** + * Refreshes the competencies. + * + * @param refresher Refresher. + */ + async refreshCompetencies(refresher?: IonRefresher): Promise { + await this.competencies.getSource().invalidateCache(); + + this.competencies.getSource().setDirty(true); + this.fetchCompetencies().finally(() => { + refresher?.complete(); + }); + } + + /** + * @inheritdoc + */ + ngOnDestroy(): void { + this.competencies.destroy(); + } + +} diff --git a/src/addons/competency/pages/competencies/competencies.ts b/src/addons/competency/pages/competencies/competencies.ts deleted file mode 100644 index 821e9db57..000000000 --- a/src/addons/competency/pages/competencies/competencies.ts +++ /dev/null @@ -1,165 +0,0 @@ -// (C) Copyright 2015 Moodle Pty Ltd. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { AfterViewInit, Component, OnDestroy, ViewChild } from '@angular/core'; -import { IonRefresher } from '@ionic/angular'; -import { CoreDomUtils } from '@services/utils/dom'; -import { CoreSplitViewComponent } from '@components/split-view/split-view'; -import { - AddonCompetencyDataForPlanPageCompetency, AddonCompetencyDataForCourseCompetenciesPageCompetency, AddonCompetency, -} from '../../services/competency'; -import { Params, ActivatedRoute } from '@angular/router'; -import { CorePageItemsListManager } from '@classes/page-items-list-manager'; -import { Translate } from '@singletons'; -import { CoreNavigator } from '@services/navigator'; -import { CoreError } from '@classes/errors/error'; - -/** - * Page that displays the list of competencies of a learning plan. - */ -@Component({ - selector: 'page-addon-competency-competencies', - templateUrl: 'competencies.html', -}) -export class AddonCompetencyCompetenciesPage implements AfterViewInit, OnDestroy { - - @ViewChild(CoreSplitViewComponent) splitView!: CoreSplitViewComponent; - - protected planId?: number; - protected courseId?: number; - protected userId?: number; - - competenciesLoaded = false; - competencies: AddonCompetencyListManager; - title = ''; - - constructor(protected route: ActivatedRoute) { - this.planId = CoreNavigator.getRouteNumberParam('planId', { route }); - if (!this.planId) { - this.courseId = CoreNavigator.getRouteNumberParam('courseId', { route }); - this.userId = CoreNavigator.getRouteNumberParam('userId', { route }); - } - - this.competencies = - new AddonCompetencyListManager(AddonCompetencyCompetenciesPage, this.planId, this.courseId, this.userId); - } - - /** - * @inheritdoc - */ - async ngAfterViewInit(): Promise { - await this.fetchCompetencies(); - - this.competencies.start(this.splitView); - } - - /** - * Fetches the competencies and updates the view. - * - * @return Promise resolved when done. - */ - protected async fetchCompetencies(): Promise { - try { - if (this.planId) { - - const response = await AddonCompetency.getLearningPlan(this.planId); - - if (response.competencycount <= 0) { - throw new CoreError(Translate.instant('addon.competency.errornocompetenciesfound')); - } - - this.title = response.plan.name; - this.userId = response.plan.userid; - - this.competencies.setItems(response.competencies); - } else if (this.courseId) { - const response = await AddonCompetency.getCourseCompetencies(this.courseId, this.userId); - this.title = Translate.instant('addon.competency.coursecompetencies'); - - this.competencies.setItems(response.competencies); - } else { - throw null; - } - } catch (error) { - CoreDomUtils.showErrorModalDefault(error, 'Error getting competencies data.'); - } - } - - /** - * Refreshes the competencies. - * - * @param refresher Refresher. - */ - async refreshCompetencies(refresher?: IonRefresher): Promise { - try { - if (this.planId) { - await AddonCompetency.invalidateLearningPlan(this.planId); - } else { - await AddonCompetency.invalidateCourseCompetencies(this.courseId!, this.userId); - } - - } finally { - this.fetchCompetencies().finally(() => { - refresher?.complete(); - }); - } - } - - /** - * @inheritdoc - */ - ngOnDestroy(): void { - this.competencies.destroy(); - } - -} - -type AddonCompetencyDataForPlanPageCompetencyFormatted = - AddonCompetencyDataForPlanPageCompetency | AddonCompetencyDataForCourseCompetenciesPageCompetency; - -/** - * Helper class to manage competencies list. - */ -class AddonCompetencyListManager extends CorePageItemsListManager { - - planId?: number; - courseId?: number; - userId?: number; - - constructor(pageComponent: unknown, planId?: number, courseId?: number, userId?: number) { - super(pageComponent); - this.planId = planId; - this.courseId = courseId; - this.userId = userId; - } - - /** - * @inheritdoc - */ - protected getItemPath(competency: AddonCompetencyDataForPlanPageCompetencyFormatted): string { - return String(competency.competency.id); - } - - /** - * @inheritdoc - */ - protected getItemQueryParams(): Params { - if (this.planId) { - return { planId: this.planId }; - } else { - return { courseId: this.courseId, userId: this.userId }; - } - } - -} diff --git a/src/addons/competency/pages/competency/competency.html b/src/addons/competency/pages/competency/competency.html index d70e9e806..a73a97e2c 100644 --- a/src/addons/competency/pages/competency/competency.html +++ b/src/addons/competency/pages/competency/competency.html @@ -10,7 +10,7 @@ - + @@ -36,9 +36,7 @@

{{ 'addon.competency.path' | translate }}

- + {{ competency.competency.comppath.framework.name }} @@ -79,7 +77,8 @@

- + + diff --git a/src/addons/competency/pages/competency/competency.module.ts b/src/addons/competency/pages/competency/competency.module.ts new file mode 100644 index 000000000..3b09be305 --- /dev/null +++ b/src/addons/competency/pages/competency/competency.module.ts @@ -0,0 +1,28 @@ +// (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 { NgModule } from '@angular/core'; + +import { CoreSharedModule } from '@/core/shared.module'; +import { AddonCompetencyCompetencyPage } from './competency.page'; + +@NgModule({ + imports: [ + CoreSharedModule, + ], + declarations: [ + AddonCompetencyCompetencyPage, + ], +}) +export class AddonCompetencyCompetencyPageModule {} diff --git a/src/addons/competency/pages/competency/competency.page.ts b/src/addons/competency/pages/competency/competency.page.ts new file mode 100644 index 000000000..e2c2a41a1 --- /dev/null +++ b/src/addons/competency/pages/competency/competency.page.ts @@ -0,0 +1,308 @@ +// (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 { AddonCompetencyHelper } from '@addons/competency/services/competency-helper'; +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { CoreCourseModuleSummary } from '@features/course/services/course'; +import { CoreUserSummary } from '@features/user/services/user'; +import { CoreSites } from '@services/sites'; +import { CoreDomUtils } from '@services/utils/dom'; +import { Translate } from '@singletons'; +import { + AddonCompetencyDataForUserCompetencySummaryWSResponse, + AddonCompetencyUserCompetencyPlan, + AddonCompetencyUserCompetency, + AddonCompetencyUserCompetencyCourse, + AddonCompetency, + AddonCompetencyDataForPlanPageCompetency, + AddonCompetencyDataForCourseCompetenciesPageCompetency, +} from '@addons/competency/services/competency'; +import { CoreNavigator } from '@services/navigator'; +import { IonRefresher } from '@ionic/angular'; +import { ContextLevel } from '@/core/constants'; +import { CoreUtils } from '@services/utils/utils'; +import { ADDON_COMPETENCY_SUMMARY_PAGE } from '@addons/competency/competency.module'; +import { CoreSwipeNavigationItemsManager } from '@classes/items-management/swipe-navigation-items-manager'; +import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker'; +import { AddonCompetencyPlanCompetenciesSource } from '@addons/competency/classes/competency-plan-competencies-source'; +import { ActivatedRouteSnapshot } from '@angular/router'; +import { AddonCompetencyCourseCompetenciesSource } from '@addons/competency/classes/competency-course-competencies-source'; + +/** + * Page that displays the competency information. + */ +@Component({ + selector: 'page-addon-competency-competency', + templateUrl: 'competency.html', +}) +export class AddonCompetencyCompetencyPage implements OnInit, OnDestroy { + + competencyLoaded = false; + competencies!: AddonCompetencyCompetenciesSwipeManager; + planStatus?: number; + coursemodules?: CoreCourseModuleSummary[]; + user?: CoreUserSummary; + competency?: AddonCompetencyDataForUserCompetencySummaryWSResponse; + userCompetency?: AddonCompetencyUserCompetencyPlan | AddonCompetencyUserCompetency | AddonCompetencyUserCompetencyCourse; + contextLevel?: string; + contextInstanceId?: number; + + constructor() { + try { + const planId = CoreNavigator.getRouteNumberParam('planId'); + + if (!planId) { + const courseId = CoreNavigator.getRequiredRouteNumberParam('courseId'); + const userId = CoreNavigator.getRouteNumberParam('userId'); + const source = CoreRoutedItemsManagerSourcesTracker.getOrCreateSource( + AddonCompetencyCourseCompetenciesSource, + [courseId, userId], + ); + + this.competencies = new AddonCompetencyCompetenciesSwipeManager(source); + + return; + } + + const source = CoreRoutedItemsManagerSourcesTracker.getOrCreateSource(AddonCompetencyPlanCompetenciesSource, [planId]); + + this.competencies = new AddonCompetencyCompetenciesSwipeManager(source); + } catch (error) { + CoreDomUtils.showErrorModal(error); + + CoreNavigator.back(); + + return; + } + } + + get competencyFrameworkUrl(): string | undefined { + if (!this.competency) { + return; + } + + const { pluginbaseurl, framework, pagecontextid } = this.competency.competency.comppath; + + return `${pluginbaseurl}/competencies.php?competencyframeworkid=${framework.id}&pagecontextid=${pagecontextid}`; + } + + get courseId(): number | undefined { + const source = this.competencies.getSource(); + + if (!(source instanceof AddonCompetencyCourseCompetenciesSource)) { + return; + } + + return source.COURSE_ID; + } + + /** + * @inheritdoc + */ + async ngOnInit(): Promise { + try { + const source = this.competencies.getSource(); + + await source.reload(); + await this.competencies.start(); + await this.fetchCompetency(); + + if (!this.competency) { + return; + } + + const name = this.competency.competency.competency.shortname; + + if (source instanceof AddonCompetencyPlanCompetenciesSource) { + this.planStatus && await CoreUtils.ignoreErrors( + AddonCompetency.logCompetencyInPlanView( + source.PLAN_ID, + this.requireCompetencyId(), + this.planStatus, + name, + source.user?.id, + ), + ); + } else { + await CoreUtils.ignoreErrors( + AddonCompetency.logCompetencyInCourseView( + source.COURSE_ID, + this.requireCompetencyId(), + name, + source.USER_ID, + ), + ); + } + } finally { + this.competencyLoaded = true; + } + } + + /** + * @inheritdoc + */ + ngOnDestroy(): void { + this.competencies.destroy(); + } + + /** + * Fetches the competency and updates the view. + * + * @return Promise resolved when done. + */ + protected async fetchCompetency(): Promise { + try { + const source = this.competencies.getSource(); + + this.competency = source instanceof AddonCompetencyPlanCompetenciesSource + ? await this.fetchCompetencySummaryFromPlan(source) + : await this.fetchCompetencySummaryFromCourse(source); + + if (this.competency.user.id != CoreSites.getCurrentSiteUserId()) { + // Get the user profile from the returned object. + this.user = this.competency.user; + } + + this.competency.evidence.forEach((evidence) => { + if (evidence.descidentifier) { + const key = 'addon.competency.' + evidence.descidentifier; + evidence.description = Translate.instant(key, { $a: evidence.desca }); + } + }); + } catch (error) { + CoreDomUtils.showErrorModalDefault(error, 'Error getting competency data.'); + } + } + + /** + * Refreshes the competency. + * + * @param refresher Refresher. + */ + async refreshCompetency(refresher: IonRefresher): Promise { + const source = this.competencies.getSource(); + + await CoreUtils.ignoreErrors( + source instanceof AddonCompetencyPlanCompetenciesSource + ? AddonCompetency.invalidateCompetencyInPlan(source.PLAN_ID, this.requireCompetencyId()) + : AddonCompetency.invalidateCompetencyInCourse(source.COURSE_ID, this.requireCompetencyId(), source.USER_ID), + ); + + this.fetchCompetency().finally(() => { + refresher?.complete(); + }); + } + + /** + * Opens the summary of a competency. + * + * @param competencyId + */ + openCompetencySummary(competencyId: number): void { + CoreNavigator.navigate( + `../${competencyId}/${ADDON_COMPETENCY_SUMMARY_PAGE}`, + { + params: { contextLevel: this.contextLevel, contextInstanceId: this.contextInstanceId }, + }, + ); + } + + /** + * Get competency id or fail. + * + * @returns Competency id. + */ + private requireCompetencyId(): number { + const selectedItem = this.competencies.getSelectedItem(); + + if (!selectedItem) { + throw new Error('Failed to get competency id from selected item'); + } + + return selectedItem.competency.id; + } + + /** + * Fetch competency summary from a plan source. + * + * @param source Plan competencies source. + * @returns Competency summary. + */ + private async fetchCompetencySummaryFromPlan( + source: AddonCompetencyPlanCompetenciesSource, + ): Promise { + const competency = await AddonCompetency.getCompetencyInPlan( + source.PLAN_ID, + this.requireCompetencyId(), + ); + + this.planStatus = competency.plan.status; + + if (competency.usercompetencysummary.usercompetency) { + competency.usercompetencysummary.usercompetency.statusname = + AddonCompetencyHelper.getCompetencyStatusName(competency.usercompetencysummary.usercompetency.status); + } + + this.contextLevel = ContextLevel.USER; + this.contextInstanceId = source.user?.id || competency.usercompetencysummary.user.id; + this.userCompetency = competency.usercompetencysummary.usercompetencyplan + || competency.usercompetencysummary.usercompetency; + + return competency.usercompetencysummary; + } + + /** + * Fetch competency summary from a course source. + * + * @param source Course competencies source. + * @returns Competency summary. + */ + private async fetchCompetencySummaryFromCourse( + source: AddonCompetencyCourseCompetenciesSource, + ): Promise { + const competency = await AddonCompetency.getCompetencyInCourse( + source.COURSE_ID, + this.requireCompetencyId(), + source.USER_ID, + ); + + this.coursemodules = competency.coursemodules; + + this.contextLevel = ContextLevel.COURSE; + this.contextInstanceId = source.COURSE_ID; + this.userCompetency = competency.usercompetencysummary.usercompetencycourse + || competency.usercompetencysummary.usercompetency; + + return competency.usercompetencysummary; + } + +} + +/** + * Helper to manage swiping within a collection of competencies. + */ +class AddonCompetencyCompetenciesSwipeManager + extends CoreSwipeNavigationItemsManager< + AddonCompetencyDataForPlanPageCompetency | AddonCompetencyDataForCourseCompetenciesPageCompetency, + AddonCompetencyPlanCompetenciesSource | AddonCompetencyCourseCompetenciesSource + > { + + /** + * @inheritdoc + */ + protected getSelectedItemPathFromRoute(route: ActivatedRouteSnapshot): string | null { + return route.params.competencyId; + } + +} diff --git a/src/addons/competency/pages/competency/competency.ts b/src/addons/competency/pages/competency/competency.ts deleted file mode 100644 index dba3905e4..000000000 --- a/src/addons/competency/pages/competency/competency.ts +++ /dev/null @@ -1,194 +0,0 @@ -// (C) Copyright 2015 Moodle Pty Ltd. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { AddonCompetencyHelper } from '@addons/competency/services/competency-helper'; -import { Component, OnInit } from '@angular/core'; -import { CoreCourseModuleSummary } from '@features/course/services/course'; -import { CoreUserSummary } from '@features/user/services/user'; -import { CoreSites } from '@services/sites'; -import { CoreDomUtils } from '@services/utils/dom'; -import { Translate } from '@singletons'; -import { - AddonCompetencyDataForUserCompetencySummaryWSResponse, - AddonCompetencyUserCompetencyPlan, - AddonCompetencyUserCompetency, - AddonCompetencyUserCompetencyCourse, - AddonCompetency, - AddonCompetencyDataForUserCompetencySummaryInPlanWSResponse, - AddonCompetencyDataForUserCompetencySummaryInCourseWSResponse, -} from '@addons/competency/services/competency'; -import { CoreNavigator } from '@services/navigator'; -import { IonRefresher } from '@ionic/angular'; -import { ContextLevel } from '@/core/constants'; -import { CoreUtils } from '@services/utils/utils'; -import { ADDON_COMPETENCY_MAIN_PAGE_NAME } from '@addons/competency/competency.module'; - -/** - * Page that displays the competency information. - */ -@Component({ - selector: 'page-addon-competency-competency', - templateUrl: 'competency.html', -}) -export class AddonCompetencyCompetencyPage implements OnInit { - - competencyLoaded = false; - competencyId!: number; - planId?: number; - courseId?: number; - userId?: number; - planStatus?: number; - coursemodules?: CoreCourseModuleSummary[]; - user?: CoreUserSummary; - competency?: AddonCompetencyDataForUserCompetencySummaryWSResponse; - userCompetency?: AddonCompetencyUserCompetencyPlan | AddonCompetencyUserCompetency | AddonCompetencyUserCompetencyCourse; - contextLevel?: string; - contextInstanceId?: number; - - /** - * @inheritdoc - */ - async ngOnInit(): Promise { - try { - this.competencyId = CoreNavigator.getRequiredRouteNumberParam('competencyId'); - this.planId = CoreNavigator.getRouteNumberParam('planId'); - if (!this.planId) { - this.courseId = CoreNavigator.getRequiredRouteNumberParam('courseId'); - this.userId = CoreNavigator.getRouteNumberParam('userId'); - } - } catch (error) { - CoreDomUtils.showErrorModal(error); - - CoreNavigator.back(); - - return; - } - - try { - await this.fetchCompetency(); - - const name = this.competency && this.competency.competency && this.competency.competency.competency && - this.competency.competency.competency.shortname; - - if (this.planId) { - CoreUtils.ignoreErrors(AddonCompetency.logCompetencyInPlanView( - this.planId, - this.competencyId, - this.planStatus!, - name, - this.userId, - )); - } else { - CoreUtils.ignoreErrors( - AddonCompetency.logCompetencyInCourseView(this.courseId!, this.competencyId, name, this.userId), - ); - } - } finally { - this.competencyLoaded = true; - } - } - - /** - * Fetches the competency and updates the view. - * - * @return Promise resolved when done. - */ - protected async fetchCompetency(): Promise { - - try { - let competency: AddonCompetencyDataForUserCompetencySummaryInPlanWSResponse | - AddonCompetencyDataForUserCompetencySummaryInCourseWSResponse; - - if (this.planId) { - this.planStatus = undefined; - - competency = await AddonCompetency.getCompetencyInPlan(this.planId, this.competencyId); - } else if (this.courseId) { - competency = await AddonCompetency.getCompetencyInCourse(this.courseId, this.competencyId, this.userId); - } else { - throw null; - } - - // Calculate the context. - if (this.courseId) { - this.contextLevel = ContextLevel.COURSE; - this.contextInstanceId = this.courseId; - } else { - this.contextLevel = ContextLevel.USER; - this.contextInstanceId = this.userId || competency.usercompetencysummary.user.id; - } - - this.competency = competency.usercompetencysummary; - this.userCompetency = this.competency.usercompetencyplan || this.competency.usercompetency; - - if ('plan' in competency) { - this.planStatus = competency.plan.status; - this.competency.usercompetency!.statusname = - AddonCompetencyHelper.getCompetencyStatusName(this.competency.usercompetency!.status); - } else { - this.userCompetency = this.competency.usercompetencycourse; - this.coursemodules = competency.coursemodules; - } - - if (this.competency.user.id != CoreSites.getCurrentSiteUserId()) { - // Get the user profile from the returned object. - this.user = this.competency.user; - } - - this.competency.evidence.forEach((evidence) => { - if (evidence.descidentifier) { - const key = 'addon.competency.' + evidence.descidentifier; - evidence.description = Translate.instant(key, { $a: evidence.desca }); - } - }); - } catch (error) { - CoreDomUtils.showErrorModalDefault(error, 'Error getting competency data.'); - } - } - - /** - * Refreshes the competency. - * - * @param refresher Refresher. - */ - async refreshCompetency(refresher: IonRefresher): Promise { - try { - if (this.planId) { - await AddonCompetency.invalidateCompetencyInPlan(this.planId, this.competencyId); - } else { - await AddonCompetency.invalidateCompetencyInCourse(this.courseId!, this.competencyId); - } - - } finally { - this.fetchCompetency().finally(() => { - refresher?.complete(); - }); - } - } - - /** - * Opens the summary of a competency. - * - * @param competencyId - */ - openCompetencySummary(competencyId: number): void { - CoreNavigator.navigateToSitePath( - ADDON_COMPETENCY_MAIN_PAGE_NAME + '/summary/' + competencyId, - { - params: { contextLevel: this.contextLevel, contextInstanceId: this.contextInstanceId }, - }, - ); - } - -} diff --git a/src/addons/competency/pages/competencysummary/competencysummary.module.ts b/src/addons/competency/pages/competencysummary/competencysummary.module.ts new file mode 100644 index 000000000..d09a75ec0 --- /dev/null +++ b/src/addons/competency/pages/competencysummary/competencysummary.module.ts @@ -0,0 +1,28 @@ +// (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 { NgModule } from '@angular/core'; + +import { CoreSharedModule } from '@/core/shared.module'; +import { AddonCompetencyCompetencySummaryPage } from './competencysummary.page'; + +@NgModule({ + imports: [ + CoreSharedModule, + ], + declarations: [ + AddonCompetencyCompetencySummaryPage, + ], +}) +export class AddonCompetencyCompetencySummaryPageModule {} diff --git a/src/addons/competency/pages/competencysummary/competencysummary.ts b/src/addons/competency/pages/competencysummary/competencysummary.page.ts similarity index 94% rename from src/addons/competency/pages/competencysummary/competencysummary.ts rename to src/addons/competency/pages/competencysummary/competencysummary.page.ts index 5119dd2c8..b490c4ed4 100644 --- a/src/addons/competency/pages/competencysummary/competencysummary.ts +++ b/src/addons/competency/pages/competencysummary/competencysummary.page.ts @@ -19,7 +19,7 @@ import { IonRefresher } from '@ionic/angular'; import { CoreNavigator } from '@services/navigator'; import { CoreDomUtils } from '@services/utils/dom'; import { CoreUtils } from '@services/utils/utils'; -import { ADDON_COMPETENCY_MAIN_PAGE_NAME } from '@addons/competency/competency.module'; +import { ADDON_COMPETENCY_SUMMARY_PAGE } from '@addons/competency/competency.module'; /** * Page that displays the competency summary. @@ -101,8 +101,8 @@ export class AddonCompetencyCompetencySummaryPage implements OnInit { * @param competencyId */ openCompetencySummary(competencyId: number): void { - CoreNavigator.navigateToSitePath( - ADDON_COMPETENCY_MAIN_PAGE_NAME + '/summary/' + competencyId, + CoreNavigator.navigate( + `../../${competencyId}/${ADDON_COMPETENCY_SUMMARY_PAGE}`, { params: { contextLevel: this.contextLevel, contextInstanceId: this.contextInstanceId }, }, diff --git a/src/addons/competency/pages/coursecompetencies/coursecompetencies.html b/src/addons/competency/pages/coursecompetencies/coursecompetencies.html index fa1cf08bc..d8f6cb615 100644 --- a/src/addons/competency/pages/coursecompetencies/coursecompetencies.html +++ b/src/addons/competency/pages/coursecompetencies/coursecompetencies.html @@ -9,35 +9,35 @@ - + - - - - + + + + {{ 'addon.competency.coursecompetencyratingsarepushedtouserplans' | translate }} - + {{ 'addon.competency.coursecompetencyratingsarenotpushedtouserplans' | translate }} - + {{ 'addon.competency.xcompetenciesproficientoutofyincourse' | translate: {$a: - {x: competencies.statistics.proficientcompetencycount, y: competencies.statistics.competencycount} } }} + {x: courseCompetencies.statistics.proficientcompetencycount, y: courseCompetencies.statistics.competencycount} } }} - + *ngIf="courseCompetencies.statistics.canmanagecoursecompetencies && courseCompetencies.statistics.leastproficientcount > 0">

{{ 'addon.competency.competenciesmostoftennotproficientincourse' | translate }}

-

+

@@ -46,7 +46,7 @@ -

+

{{ 'addon.competency.coursecompetencies' | translate }}

@@ -57,13 +57,13 @@
- -
- - + +

@@ -85,8 +85,7 @@

{{ 'addon.competency.path' | translate }}

- {{ competency.comppath.framework.name }} @@ -104,7 +103,7 @@

-
+

{{ 'addon.competency.uponcoursecompletion' | translate }}

{{ ruleoutcome.text }} diff --git a/src/addons/competency/pages/coursecompetencies/coursecompetencies.page.ts b/src/addons/competency/pages/coursecompetencies/coursecompetencies.page.ts index 9302289c4..79924d2a1 100644 --- a/src/addons/competency/pages/coursecompetencies/coursecompetencies.page.ts +++ b/src/addons/competency/pages/coursecompetencies/coursecompetencies.page.ts @@ -12,15 +12,20 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Component, OnInit } from '@angular/core'; -import { AddonCompetencyDataForCourseCompetenciesPageWSResponse, AddonCompetency } from '@addons/competency/services/competency'; -import { AddonCompetencyHelper } from '@addons/competency/services/competency-helper'; +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { + AddonCompetencyDataForCourseCompetenciesPageWSResponse, + AddonCompetencyDataForCourseCompetenciesPageCompetency, +} from '@addons/competency/services/competency'; import { CoreUserProfile } from '@features/user/services/user'; import { IonRefresher } from '@ionic/angular'; import { CoreNavigator } from '@services/navigator'; import { CoreDomUtils } from '@services/utils/dom'; import { ContextLevel } from '@/core/constants'; -import { ADDON_COMPETENCY_MAIN_PAGE_NAME } from '@addons/competency/competency.module'; +import { ADDON_COMPETENCY_SUMMARY_PAGE } from '@addons/competency/competency.module'; +import { CoreListItemsManager } from '@classes/items-management/list-items-manager'; +import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker'; +import { AddonCompetencyCourseCompetenciesSource } from '@addons/competency/classes/competency-course-competencies-source'; /** * Page that displays the list of competencies of a course. @@ -29,22 +34,23 @@ import { ADDON_COMPETENCY_MAIN_PAGE_NAME } from '@addons/competency/competency.m selector: 'page-addon-competency-coursecompetencies', templateUrl: 'coursecompetencies.html', }) -export class AddonCompetencyCourseCompetenciesPage implements OnInit { +export class AddonCompetencyCourseCompetenciesPage implements OnInit, OnDestroy { - competenciesLoaded = false; - competencies?: AddonCompetencyDataForCourseCompetenciesPageWSResponse; - user?: CoreUserProfile; - courseId!: number; + competencies!: CoreListItemsManager< + AddonCompetencyDataForCourseCompetenciesPageCompetency, + AddonCompetencyCourseCompetenciesSource + >; - protected userId?: number; - - /** - * View loaded. - */ - ngOnInit(): void { + constructor() { try { - this.courseId = CoreNavigator.getRequiredRouteNumberParam('courseId'); - this.userId = CoreNavigator.getRouteNumberParam('userId'); + const courseId = CoreNavigator.getRequiredRouteNumberParam('courseId'); + const userId = CoreNavigator.getRouteNumberParam('userId'); + const source = CoreRoutedItemsManagerSourcesTracker.getOrCreateSource( + AddonCompetencyCourseCompetenciesSource, + [courseId, userId], + ); + + this.competencies = new CoreListItemsManager(source, AddonCompetencyCourseCompetenciesPage); } catch (error) { CoreDomUtils.showErrorModal(error); @@ -52,10 +58,50 @@ export class AddonCompetencyCourseCompetenciesPage implements OnInit { return; } + } - this.fetchCourseCompetencies().finally(() => { - this.competenciesLoaded = true; - }); + get courseCompetencies(): AddonCompetencyDataForCourseCompetenciesPageWSResponse | undefined { + return this.competencies.getSource().courseCompetencies; + } + + get courseId(): number { + return this.competencies.getSource().COURSE_ID; + } + + get user(): CoreUserProfile | undefined { + return this.competencies.getSource().user; + } + + get showLeastProficientCompetencies(): boolean { + return !!this.courseCompetencies?.statistics.canmanagecoursecompetencies + && this.courseCompetencies?.statistics.leastproficientcount > 0; + } + + /** + * @inheritdoc + */ + async ngOnInit(): Promise { + await this.fetchCourseCompetencies(); + await this.competencies.start(); + } + + /** + * @inheritdoc + */ + ngOnDestroy(): void { + this.competencies.destroy(); + } + + /** + * Get competency framework url. + * + * @param competency Competency. + * @returns Competency framework url. + */ + getCompetencyFrameworkUrl(competency: AddonCompetencyDataForCourseCompetenciesPageCompetency): string { + const { pluginbaseurl, framework, pagecontextid } = competency.comppath; + + return `${pluginbaseurl}/competencies.php?competencyframeworkid=${framework.id}&pagecontextid=${pagecontextid}`; } /** @@ -65,40 +111,27 @@ export class AddonCompetencyCourseCompetenciesPage implements OnInit { */ protected async fetchCourseCompetencies(): Promise { try { - this.competencies = await AddonCompetency.getCourseCompetencies(this.courseId, this.userId); - - // Get the user profile image. - this.user = await AddonCompetencyHelper.getProfile(this.userId); + await this.competencies.getSource().reload(); } catch (error) { CoreDomUtils.showErrorModalDefault(error, 'Error getting course competencies data.'); } } - /** - * Opens a competency. - * - * @param competencyId - */ - openCompetency(competencyId: number): void { - CoreNavigator.navigateToSitePath( - ADDON_COMPETENCY_MAIN_PAGE_NAME + '/competencies/' + competencyId, - { - params: { courseId: this.courseId, userId: this.userId }, - }, - ); - } - /** * Opens the summary of a competency. * * @param competencyId */ openCompetencySummary(competencyId: number): void { - CoreNavigator.navigateToSitePath(ADDON_COMPETENCY_MAIN_PAGE_NAME + '/summary/' + competencyId, { - params: { - contextLevel: ContextLevel.COURSE, - contextInstanceId: this.courseId, - } }); + CoreNavigator.navigateToSitePath( + `./${competencyId}/${ADDON_COMPETENCY_SUMMARY_PAGE}`, + { + params: { + contextLevel: ContextLevel.COURSE, + contextInstanceId: this.courseId, + }, + }, + ); } /** @@ -106,11 +139,11 @@ export class AddonCompetencyCourseCompetenciesPage implements OnInit { * * @param refresher Refresher. */ - refreshCourseCompetencies(refresher?: IonRefresher): void { - AddonCompetency.invalidateCourseCompetencies(this.courseId, this.userId).finally(() => { - this.fetchCourseCompetencies().finally(() => { - refresher?.complete(); - }); + async refreshCourseCompetencies(refresher?: IonRefresher): Promise { + await this.competencies.getSource().invalidateCache(); + + this.fetchCourseCompetencies().finally(() => { + refresher?.complete(); }); } diff --git a/src/addons/competency/pages/plan/plan.html b/src/addons/competency/pages/plan/plan.html index ff5035a62..4c55d843d 100644 --- a/src/addons/competency/pages/plan/plan.html +++ b/src/addons/competency/pages/plan/plan.html @@ -8,11 +8,11 @@ - - + + - + @@ -74,9 +74,8 @@

{{ 'addon.competency.nocompetencies' | translate }}

- +

{{competency.competency.shortname}} {{competency.competency.idnumber}}

diff --git a/src/addons/competency/pages/plan/plan.ts b/src/addons/competency/pages/plan/plan.ts index aafe9c4d8..ee1c9a11f 100644 --- a/src/addons/competency/pages/plan/plan.ts +++ b/src/addons/competency/pages/plan/plan.ts @@ -12,14 +12,17 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Component, OnInit } from '@angular/core'; +import { Component, OnDestroy, OnInit } from '@angular/core'; import { CoreDomUtils } from '@services/utils/dom'; -import { AddonCompetencyDataForPlanPageWSResponse, AddonCompetency } from '../../services/competency'; -import { AddonCompetencyHelper } from '../../services/competency-helper'; +import { AddonCompetencyDataForPlanPageCompetency, AddonCompetencyDataForPlanPageWSResponse } from '../../services/competency'; import { CoreNavigator } from '@services/navigator'; import { CoreUserProfile } from '@features/user/services/user'; import { IonRefresher } from '@ionic/angular'; -import { ADDON_COMPETENCY_MAIN_PAGE_NAME } from '@addons/competency/competency.module'; +import { CoreSwipeNavigationItemsManager } from '@classes/items-management/swipe-navigation-items-manager'; +import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker'; +import { AddonCompetencyPlansSource } from '@addons/competency/classes/competency-plans-source'; +import { CoreListItemsManager } from '@classes/items-management/list-items-manager'; +import { AddonCompetencyPlanCompetenciesSource } from '@addons/competency/classes/competency-plan-competencies-source'; /** * Page that displays a learning plan. @@ -28,19 +31,26 @@ import { ADDON_COMPETENCY_MAIN_PAGE_NAME } from '@addons/competency/competency.m selector: 'page-addon-competency-plan', templateUrl: 'plan.html', }) -export class AddonCompetencyPlanPage implements OnInit { +export class AddonCompetencyPlanPage implements OnInit, OnDestroy { - protected planId!: number; - loaded = false; - plan?: AddonCompetencyDataForPlanPageWSResponse; - user?: CoreUserProfile; + plans!: CoreSwipeNavigationItemsManager; + competencies!: CoreListItemsManager; - /** - * @inheritdoc - */ - ngOnInit(): void { + constructor() { try { - this.planId = CoreNavigator.getRequiredRouteNumberParam('planId'); + const planId = CoreNavigator.getRequiredRouteNumberParam('planId'); + const userId = CoreNavigator.getRouteNumberParam('userId'); + const plansSource = CoreRoutedItemsManagerSourcesTracker.getOrCreateSource( + AddonCompetencyPlansSource, + [userId], + ); + const competenciesSource = CoreRoutedItemsManagerSourcesTracker.getOrCreateSource( + AddonCompetencyPlanCompetenciesSource, + [planId], + ); + + this.competencies = new CoreListItemsManager(competenciesSource, AddonCompetencyPlanPage); + this.plans = new CoreSwipeNavigationItemsManager(plansSource); } catch (error) { CoreDomUtils.showErrorModal(error); @@ -48,10 +58,31 @@ export class AddonCompetencyPlanPage implements OnInit { return; } + } - this.fetchLearningPlan().finally(() => { - this.loaded = true; - }); + get plan(): AddonCompetencyDataForPlanPageWSResponse | undefined { + return this.competencies.getSource().plan; + } + + get user(): CoreUserProfile | undefined { + return this.competencies.getSource().user; + } + + /** + * @inheritdoc + */ + async ngOnInit(): Promise { + await this.fetchLearningPlan(); + await this.plans.start(); + await this.competencies.start(); + } + + /** + * @inheritdoc + */ + ngOnDestroy(): void { + this.plans.destroy(); + this.competencies.destroy(); } /** @@ -61,40 +92,22 @@ export class AddonCompetencyPlanPage implements OnInit { */ protected async fetchLearningPlan(): Promise { try { - const plan = await AddonCompetency.getLearningPlan(this.planId); - plan.plan.statusname = AddonCompetencyHelper.getPlanStatusName(plan.plan.status); - - // Get the user profile image. - this.user = await AddonCompetencyHelper.getProfile(plan.plan.userid); - - this.plan = plan; + await this.competencies.getSource().reload(); } catch (error) { CoreDomUtils.showErrorModalDefault(error, 'Error getting learning plan data.'); } } - /** - * Navigates to a particular competency. - * - * @param competencyId - */ - openCompetency(competencyId: number): void { - CoreNavigator.navigateToSitePath( - ADDON_COMPETENCY_MAIN_PAGE_NAME + '/competencies/' + competencyId, - { params: { planId: this.planId } }, - ); - } - /** * Refreshes the learning plan. * * @param refresher Refresher. */ - refreshLearningPlan(refresher: IonRefresher): void { - AddonCompetency.invalidateLearningPlan(this.planId).finally(() => { - this.fetchLearningPlan().finally(() => { - refresher?.complete(); - }); + async refreshLearningPlan(refresher: IonRefresher): Promise { + await this.competencies.getSource().invalidateCache(); + + this.fetchLearningPlan().finally(() => { + refresher?.complete(); }); } diff --git a/src/addons/competency/pages/planlist/planlist.ts b/src/addons/competency/pages/planlist/planlist.ts index 4e414fde7..1cec25f8c 100644 --- a/src/addons/competency/pages/planlist/planlist.ts +++ b/src/addons/competency/pages/planlist/planlist.ts @@ -12,14 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { AfterViewInit, Component, OnDestroy, OnInit, ViewChild } from '@angular/core'; +import { AfterViewInit, Component, OnDestroy, ViewChild } from '@angular/core'; import { IonRefresher } from '@ionic/angular'; import { CoreDomUtils } from '@services/utils/dom'; import { CoreSplitViewComponent } from '@components/split-view/split-view'; -import { AddonCompetencyProvider, AddonCompetencyPlan, AddonCompetency } from '../../services/competency'; -import { AddonCompetencyHelper } from '../../services/competency-helper'; import { CoreNavigator } from '@services/navigator'; -import { CorePageItemsListManager } from '@classes/page-items-list-manager'; +import { AddonCompetencyPlanFormatted, AddonCompetencyPlansSource } from '@addons/competency/classes/competency-plans-source'; +import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker'; +import { CoreListItemsManager } from '@classes/items-management/list-items-manager'; /** * Page that displays the list of learning plans. @@ -28,22 +28,17 @@ import { CorePageItemsListManager } from '@classes/page-items-list-manager'; selector: 'page-addon-competency-planlist', templateUrl: 'planlist.html', }) -export class AddonCompetencyPlanListPage implements OnInit, AfterViewInit, OnDestroy { +export class AddonCompetencyPlanListPage implements AfterViewInit, OnDestroy { @ViewChild(CoreSplitViewComponent) splitView!: CoreSplitViewComponent; - protected userId?: number; - plans: AddonCompetencyPlanListManager; + plans: CoreListItemsManager; constructor() { - this.plans = new AddonCompetencyPlanListManager(AddonCompetencyPlanListPage); - } + const userId = CoreNavigator.getRouteNumberParam('userId'); + const source = CoreRoutedItemsManagerSourcesTracker.getOrCreateSource(AddonCompetencyPlansSource, [userId]); - /** - * @inheritdoc - */ - ngOnInit(): void { - this.userId = CoreNavigator.getRouteNumberParam('userId'); + this.plans = new CoreListItemsManager(source, AddonCompetencyPlanListPage); } /** @@ -62,23 +57,7 @@ export class AddonCompetencyPlanListPage implements OnInit, AfterViewInit, OnDes */ protected async fetchLearningPlans(): Promise { try { - const plans = await AddonCompetency.getLearningPlans(this.userId); - plans.forEach((plan: AddonCompetencyPlanFormatted) => { - plan.statusname = AddonCompetencyHelper.getPlanStatusName(plan.status); - switch (plan.status) { - case AddonCompetencyProvider.STATUS_ACTIVE: - plan.statuscolor = 'success'; - break; - case AddonCompetencyProvider.STATUS_COMPLETE: - plan.statuscolor = 'danger'; - break; - default: - plan.statuscolor = 'warning'; - break; - } - }); - this.plans.setItems(plans); - + await this.plans.load(); } catch (error) { CoreDomUtils.showErrorModalDefault(error, 'Error getting learning plans data.'); } @@ -89,11 +68,12 @@ export class AddonCompetencyPlanListPage implements OnInit, AfterViewInit, OnDes * * @param refresher Refresher. */ - refreshLearningPlans(refresher: IonRefresher): void { - AddonCompetency.invalidateLearningPlans(this.userId).finally(() => { - this.fetchLearningPlans().finally(() => { - refresher?.complete(); - }); + async refreshLearningPlans(refresher: IonRefresher): Promise { + await this.plans.getSource().invalidateCache(); + + this.plans.getSource().setDirty(true); + this.fetchLearningPlans().finally(() => { + refresher?.complete(); }); } @@ -105,28 +85,3 @@ export class AddonCompetencyPlanListPage implements OnInit, AfterViewInit, OnDes } } - -/** - * Competency plan with some calculated data. - */ -type AddonCompetencyPlanFormatted = AddonCompetencyPlan & { - statuscolor?: string; // Calculated in the app. Color of the plan's status. -}; - -/** - * Helper class to manage plan list. - */ -class AddonCompetencyPlanListManager extends CorePageItemsListManager { - - constructor(pageComponent: unknown) { - super(pageComponent); - } - - /** - * @inheritdoc - */ - protected getItemPath(plan: AddonCompetencyPlanFormatted): string { - return String(plan.id); - } - -} diff --git a/src/addons/competency/services/handlers/competency-link.ts b/src/addons/competency/services/handlers/competency-link.ts index 2ae81e974..15bc6cc1a 100644 --- a/src/addons/competency/services/handlers/competency-link.ts +++ b/src/addons/competency/services/handlers/competency-link.ts @@ -12,10 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { ADDON_COMPETENCY_MAIN_PAGE_NAME } from '@addons/competency/competency.module'; +import { ADDON_COMPETENCY_COMPETENCIES_PAGE, ADDON_COMPETENCY_LEARNING_PLANS_PAGE } from '@addons/competency/competency.module'; import { Injectable } from '@angular/core'; import { CoreContentLinksHandlerBase } from '@features/contentlinks/classes/base-handler'; import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate'; +import { COURSE_PAGE_NAME } from '@features/course/course.module'; import { CoreNavigator } from '@services/navigator'; import { makeSingleton } from '@singletons'; import { AddonCompetency } from '../competency'; @@ -37,17 +38,29 @@ export class AddonCompetencyCompetencyLinkHandlerService extends CoreContentLink return [{ action: (siteId: string): void => { - const pageParams = { - planId: params.planid, - courseId: courseId, - userId: params.userid, - }; + if (courseId) { + CoreNavigator.navigateToSitePath( + `${COURSE_PAGE_NAME}/${courseId}/${ADDON_COMPETENCY_COMPETENCIES_PAGE}`, + { + params: { userId: params.userid }, + siteId, + }, + ); - CoreNavigator.navigateToSitePath( - ADDON_COMPETENCY_MAIN_PAGE_NAME + '/competencies/' + params.competencyid, - { params: pageParams, siteId }, - ); + return; + } + if (params.planid) { + CoreNavigator.navigateToSitePath( + `${ADDON_COMPETENCY_LEARNING_PLANS_PAGE}/competencies/${params.planid}`, + { + params: { userId: params.userid }, + siteId, + }, + ); + + return; + } }, }]; } diff --git a/src/addons/competency/services/handlers/course-option.ts b/src/addons/competency/services/handlers/course-option.ts index d46c2d6fa..1020fcf09 100644 --- a/src/addons/competency/services/handlers/course-option.ts +++ b/src/addons/competency/services/handlers/course-option.ts @@ -25,7 +25,7 @@ import { CoreCourseUserAdminOrNavOptionIndexed } from '@features/courses/service import { CoreEnrolledCourseDataWithExtraInfoAndOptions } from '@features/courses/services/courses-helper'; import { CoreFilterHelper } from '@features/filter/services/filter-helper'; import { ContextLevel } from '@/core/constants'; -import { ADDON_COMPETENCY_MAIN_PAGE_NAME } from '@addons/competency/competency.module'; +import { ADDON_COMPETENCY_COMPETENCIES_PAGE } from '@addons/competency/competency.module'; /** * Course nav handler. @@ -75,7 +75,7 @@ export class AddonCompetencyCourseOptionHandlerService implements CoreCourseOpti return { title: 'addon.competency.competencies', class: 'addon-competency-course-handler', - page: ADDON_COMPETENCY_MAIN_PAGE_NAME, + page: ADDON_COMPETENCY_COMPETENCIES_PAGE, }; } diff --git a/src/addons/competency/services/handlers/plan-link.ts b/src/addons/competency/services/handlers/plan-link.ts index 7ac937d62..e38bb136a 100644 --- a/src/addons/competency/services/handlers/plan-link.ts +++ b/src/addons/competency/services/handlers/plan-link.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { ADDON_COMPETENCY_MAIN_PAGE_NAME } from '@addons/competency/competency.module'; +import { ADDON_COMPETENCY_LEARNING_PLANS_PAGE } from '@addons/competency/competency.module'; import { Injectable } from '@angular/core'; import { CoreContentLinksHandlerBase } from '@features/contentlinks/classes/base-handler'; import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate'; @@ -36,7 +36,7 @@ export class AddonCompetencyPlanLinkHandlerService extends CoreContentLinksHandl return [{ action: (siteId: string): void => { CoreNavigator.navigateToSitePath( - ADDON_COMPETENCY_MAIN_PAGE_NAME + '/' + params.id, + `${ADDON_COMPETENCY_LEARNING_PLANS_PAGE}/${params.id}`, { siteId }, ); }, diff --git a/src/addons/competency/services/handlers/plans-link.ts b/src/addons/competency/services/handlers/plans-link.ts index bfa9e8fd5..2f5bddeb4 100644 --- a/src/addons/competency/services/handlers/plans-link.ts +++ b/src/addons/competency/services/handlers/plans-link.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { ADDON_COMPETENCY_MAIN_PAGE_NAME } from '@addons/competency/competency.module'; +import { ADDON_COMPETENCY_LEARNING_PLANS_PAGE } from '@addons/competency/competency.module'; import { Injectable } from '@angular/core'; import { CoreContentLinksHandlerBase } from '@features/contentlinks/classes/base-handler'; import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate'; @@ -36,7 +36,7 @@ export class AddonCompetencyPlansLinkHandlerService extends CoreContentLinksHand return [{ action: (siteId: string): void => { CoreNavigator.navigateToSitePath( - ADDON_COMPETENCY_MAIN_PAGE_NAME, + ADDON_COMPETENCY_LEARNING_PLANS_PAGE, { params: { userId: params.userid }, siteId }, ); diff --git a/src/addons/competency/services/handlers/push-click.ts b/src/addons/competency/services/handlers/push-click.ts index 12f2f7a4c..8172821e2 100644 --- a/src/addons/competency/services/handlers/push-click.ts +++ b/src/addons/competency/services/handlers/push-click.ts @@ -12,8 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { ADDON_COMPETENCY_MAIN_PAGE_NAME } from '@addons/competency/competency.module'; +import { ADDON_COMPETENCY_COMPETENCIES_PAGE, ADDON_COMPETENCY_LEARNING_PLANS_PAGE } from '@addons/competency/competency.module'; import { Injectable } from '@angular/core'; +import { COURSE_PAGE_NAME } from '@features/course/course.module'; import { CorePushNotificationsClickHandler } from '@features/pushnotifications/services/push-delegate'; import { CorePushNotificationsNotificationBasicData } from '@features/pushnotifications/services/pushnotifications'; import { CoreNavigator } from '@services/navigator'; @@ -56,7 +57,7 @@ export class AddonCompetencyPushClickHandlerService implements CorePushNotificat await CoreUtils.ignoreErrors(AddonCompetency.invalidateLearningPlan(planId, notification.site)); - await CoreNavigator.navigateToSitePath(ADDON_COMPETENCY_MAIN_PAGE_NAME + '/' + planId, { + await CoreNavigator.navigateToSitePath(`${ADDON_COMPETENCY_LEARNING_PLANS_PAGE}/${planId}`, { siteId: notification.site, }); @@ -71,15 +72,30 @@ export class AddonCompetencyPushClickHandlerService implements CorePushNotificat const userId = Number(contextUrlParams.userid); await CoreUtils.ignoreErrors(AddonCompetency.invalidateCompetencyInPlan(planId, competencyId, notification.site)); - await CoreNavigator.navigateToSitePath( - ADDON_COMPETENCY_MAIN_PAGE_NAME + '/competencies/' + competencyId, - { - params: { planId, courseId, userId }, - siteId: notification.site, - }, - ); - return; + if (courseId) { + await CoreNavigator.navigateToSitePath( + `${COURSE_PAGE_NAME}/${courseId}/${ADDON_COMPETENCY_COMPETENCIES_PAGE}/${competencyId}`, + { + params: { userId }, + siteId: notification.site, + }, + ); + + return; + } + + if (planId) { + await CoreNavigator.navigateToSitePath( + `${ADDON_COMPETENCY_LEARNING_PLANS_PAGE}/competencies/${planId}/${competencyId}`, + { + params: { userId }, + siteId: notification.site, + }, + ); + + return; + } } // Open the list of plans. @@ -87,7 +103,7 @@ export class AddonCompetencyPushClickHandlerService implements CorePushNotificat await CoreUtils.ignoreErrors(AddonCompetency.invalidateLearningPlans(userId, notification.site)); - await CoreNavigator.navigateToSitePath(ADDON_COMPETENCY_MAIN_PAGE_NAME, { + await CoreNavigator.navigateToSitePath(ADDON_COMPETENCY_LEARNING_PLANS_PAGE, { params: { userId }, siteId: notification.site, }); diff --git a/src/addons/competency/services/handlers/user-competency-link.ts b/src/addons/competency/services/handlers/user-competency-link.ts index 537984f28..adbab8a92 100644 --- a/src/addons/competency/services/handlers/user-competency-link.ts +++ b/src/addons/competency/services/handlers/user-competency-link.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { ADDON_COMPETENCY_MAIN_PAGE_NAME } from '@addons/competency/competency.module'; +import { ADDON_COMPETENCY_COMPETENCIES_PAGE, ADDON_COMPETENCY_SUMMARY_PAGE } from '@addons/competency/competency.module'; import { Injectable } from '@angular/core'; import { CoreContentLinksHandlerBase } from '@features/contentlinks/classes/base-handler'; import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate'; @@ -33,14 +33,12 @@ export class AddonCompetencyUserCompetencyLinkHandlerService extends CoreContent * @inheritdoc */ getActions(siteIds: string[], url: string, params: Record): CoreContentLinksAction[] { - return [{ action: (siteId: string): void => { CoreNavigator.navigateToSitePath( - ADDON_COMPETENCY_MAIN_PAGE_NAME + '/summary/' + params.id, + `${ADDON_COMPETENCY_COMPETENCIES_PAGE}/${params.id}/${ADDON_COMPETENCY_SUMMARY_PAGE}`, { siteId }, ); - }, }]; } diff --git a/src/addons/competency/services/handlers/user.ts b/src/addons/competency/services/handlers/user.ts index 356fbf94d..2bd346028 100644 --- a/src/addons/competency/services/handlers/user.ts +++ b/src/addons/competency/services/handlers/user.ts @@ -12,10 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { ADDON_COMPETENCY_MAIN_PAGE_NAME } from '@addons/competency/competency.module'; +import { ADDON_COMPETENCY_COMPETENCIES_PAGE, ADDON_COMPETENCY_LEARNING_PLANS_PAGE } from '@addons/competency/competency.module'; import { Injectable } from '@angular/core'; +import { COURSE_PAGE_NAME } from '@features/course/course.module'; import { CoreUserProfile } from '@features/user/services/user'; import { CoreUserProfileHandler, CoreUserDelegateService, CoreUserProfileHandlerData } from '@features/user/services/user-delegate'; +import { PARTICIPANTS_PAGE_NAME } from '@features/user/user.module'; import { CoreNavigator } from '@services/navigator'; import { makeSingleton } from '@singletons'; import { AddonCompetency } from '../competency'; @@ -69,12 +71,8 @@ export class AddonCompetencyUserHandlerService implements CoreUserProfileHandler event.preventDefault(); event.stopPropagation(); CoreNavigator.navigateToSitePath( - ADDON_COMPETENCY_MAIN_PAGE_NAME + '/course/' + courseId, - { - params: { userId: user.id }, - }, + [COURSE_PAGE_NAME, courseId, PARTICIPANTS_PAGE_NAME, user.id, ADDON_COMPETENCY_COMPETENCIES_PAGE].join('/'), ); - }, }; } else { @@ -85,7 +83,7 @@ export class AddonCompetencyUserHandlerService implements CoreUserProfileHandler action: (event, user): void => { event.preventDefault(); event.stopPropagation(); - CoreNavigator.navigateToSitePath(ADDON_COMPETENCY_MAIN_PAGE_NAME, { + CoreNavigator.navigateToSitePath(ADDON_COMPETENCY_LEARNING_PLANS_PAGE, { params: { userId: user.id }, }); }, diff --git a/src/addons/mod/chat/classes/chat-sessions-source.ts b/src/addons/mod/chat/classes/chat-sessions-source.ts new file mode 100644 index 000000000..e53b4be01 --- /dev/null +++ b/src/addons/mod/chat/classes/chat-sessions-source.ts @@ -0,0 +1,142 @@ +// (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 { Params } from '@angular/router'; +import { CoreRoutedItemsManagerSource } from '@classes/items-management/routed-items-manager-source'; +import { CoreUser } from '@features/user/services/user'; +import { CoreGroupInfo, CoreGroups } from '@services/groups'; +import { CoreUtils } from '@services/utils/utils'; +import { Translate } from '@singletons'; +import { AddonModChat, AddonModChatSession, AddonModChatSessionUser } from '../services/chat'; + +/** + * Provides a collection of sessions. + */ +export class AddonModChatSessionsSource extends CoreRoutedItemsManagerSource { + + readonly COURSE_ID: number; + readonly CHAT_ID: number; + readonly CM_ID: number; + + showAll = false; + groupId = 0; + groupInfo?: CoreGroupInfo; + + constructor(courseId: number, chatId: number, cmId: number) { + super(); + + this.COURSE_ID = courseId; + this.CHAT_ID = chatId; + this.CM_ID = cmId; + } + + /** + * Invalidate chat cache. + */ + async invalidateCache(): Promise { + await CoreUtils.ignoreErrors(CoreUtils.allPromises([ + CoreGroups.invalidateActivityGroupInfo(this.CM_ID), + AddonModChat.invalidateSessions(this.CHAT_ID, this.groupId, this.showAll), + ])); + } + + /** + * @inheritdoc + */ + protected async loadPageItems(): Promise<{ items: AddonModChatSessionFormatted[] }> { + this.groupInfo = await CoreGroups.getActivityGroupInfo(this.CM_ID, false); + + this.groupId = CoreGroups.validateGroupId(this.groupId, this.groupInfo); + + const sessions = await AddonModChat.getSessions(this.CHAT_ID, this.groupId, this.showAll, { cmId: this.CM_ID }); + + // Fetch user profiles. + const promises: Promise[] = []; + + const formattedSessions = sessions.map((session: AddonModChatSessionFormatted) => { + session.duration = session.sessionend - session.sessionstart; + session.sessionusers.forEach((sessionUser) => { + // The WS does not return the user name, fetch user profile. + promises.push(this.loadUserFullname(sessionUser)); + }); + + // If session has more than 4 users we display a "Show more" link. + session.allsessionusers = session.sessionusers; + if (session.sessionusers.length > 4) { + session.sessionusers = session.allsessionusers.slice(0, 3); + } + + return session; + }); + + await Promise.all(promises); + + return { items: formattedSessions }; + } + + /** + * @inheritdoc + */ + getItemPath(session: AddonModChatSessionFormatted): string { + return `${session.sessionstart}/${session.sessionend}`; + } + + /** + * @inheritdoc + */ + getItemQueryParams(): Params { + return { + chatId: this.CHAT_ID, + groupId: this.groupId, + }; + } + + /** + * Load the fullname of a user. + * + * @param id User ID. + * @return Promise resolved when done. + */ + protected async loadUserFullname(sessionUser: AddonModChatUserSessionFormatted): Promise { + if (sessionUser.userfullname) { + return; + } + + try { + const user = await CoreUser.getProfile(sessionUser.userid, this.COURSE_ID, true); + + sessionUser.userfullname = user.fullname; + } catch { + // Error getting profile, most probably the user is deleted. + sessionUser.userfullname = Translate.instant('core.deleteduser') + ' ' + sessionUser.userid; + } + } + +} + +/** + * Fields added to chat session in this view. + */ +export type AddonModChatSessionFormatted = Omit & { + duration?: number; // Session duration. + sessionusers: AddonModChatUserSessionFormatted[]; + allsessionusers?: AddonModChatUserSessionFormatted[]; // All session users. +}; + +/** + * Fields added to user session in this view. + */ +export type AddonModChatUserSessionFormatted = AddonModChatSessionUser & { + userfullname?: string; // User full name. +}; diff --git a/src/addons/mod/chat/pages/sessions/sessions.html b/src/addons/mod/chat/pages/sessions/sessions.html index d4f7bf45e..53115aac3 100644 --- a/src/addons/mod/chat/pages/sessions/sessions.html +++ b/src/addons/mod/chat/pages/sessions/sessions.html @@ -19,7 +19,7 @@ {{'core.groupsseparate' | translate }} {{'core.groupsvisible' | translate }} - {{groupOpt.name}} @@ -29,7 +29,7 @@ {{ 'addon.mod_chat.showincompletesessions' | translate }} - + ; constructor() { - this.sessions = new AddonChatSessionsManager(AddonModChatSessionsPage); - } - - /** - * @inheritdoc - */ - async ngAfterViewInit(): Promise { try { - this.courseId = CoreNavigator.getRequiredRouteNumberParam('courseId'); - this.cmId = CoreNavigator.getRequiredRouteNumberParam('cmId'); - this.chatId = CoreNavigator.getRequiredRouteNumberParam('chatId'); - this.sessions.setChatId(this.chatId); + const courseId = CoreNavigator.getRequiredRouteNumberParam('courseId'); + const chatId = CoreNavigator.getRequiredRouteNumberParam('chatId'); + const cmId = CoreNavigator.getRequiredRouteNumberParam('cmId'); + const source = CoreRoutedItemsManagerSourcesTracker.getOrCreateSource( + AddonModChatSessionsSource, + [courseId, chatId, cmId], + ); + + this.sessions = new CoreListItemsManager(source, AddonModChatSessionsPage); } catch (error) { CoreDomUtils.showErrorModal(error); @@ -65,7 +53,32 @@ export class AddonModChatSessionsPage implements AfterViewInit, OnDestroy { return; } + } + get groupId(): number { + return this.sessions.getSource().groupId; + } + + set groupId(value: number) { + this.sessions.getSource().groupId = value; + } + + get showAll(): boolean { + return this.sessions.getSource().showAll; + } + + set showAll(value: boolean) { + this.sessions.getSource().showAll = value; + } + + get groupInfo(): CoreGroupInfo | undefined { + return this.sessions.getSource().groupInfo; + } + + /** + * @inheritdoc + */ + async ngAfterViewInit(): Promise { await this.fetchSessions(); this.sessions.start(this.splitView); @@ -75,66 +88,27 @@ export class AddonModChatSessionsPage implements AfterViewInit, OnDestroy { * Fetch chat sessions. * * @param showLoading Display a loading modal. - * @return Promise resolved when done. */ - async fetchSessions(showLoading?: boolean): Promise { - const modal = showLoading ? await CoreDomUtils.showModalLoading() : null; - + async fetchSessions(): Promise { try { - this.groupInfo = await CoreGroups.getActivityGroupInfo(this.cmId, false); - - this.groupId = CoreGroups.validateGroupId(this.groupId, this.groupInfo); - this.sessions.setGroupId(this.groupId); - - const sessions = await AddonModChat.getSessions(this.chatId, this.groupId, this.showAll, { cmId: this.cmId }); - - // Fetch user profiles. - const promises: Promise[] = []; - - const formattedSessions = sessions.map((session: AddonModChatSessionFormatted) => { - session.duration = session.sessionend - session.sessionstart; - session.sessionusers.forEach((sessionUser) => { - // The WS does not return the user name, fetch user profile. - promises.push(this.loadUserFullname(sessionUser)); - }); - - // If session has more than 4 users we display a "Show more" link. - session.allsessionusers = session.sessionusers; - if (session.sessionusers.length > 4) { - session.sessionusers = session.allsessionusers.slice(0, 3); - } - - return session; - }); - - await Promise.all(promises); - - this.sessions.setItems(formattedSessions); + await this.sessions.load(); } catch (error) { CoreDomUtils.showErrorModalDefault(error, 'core.errorloadingcontent', true); - } finally { - modal?.dismiss(); } } /** - * Load the fullname of a user. - * - * @param id User ID. - * @return Promise resolved when done. + * Reload chat sessions. */ - protected async loadUserFullname(sessionUser: AddonModChatUserSessionFormatted): Promise { - if (sessionUser.userfullname) { - return; - } + async reloadSessions(): Promise { + const modal = await CoreDomUtils.showModalLoading(); try { - const user = await CoreUser.getProfile(sessionUser.userid, this.courseId, true); - - sessionUser.userfullname = user.fullname; - } catch { - // Error getting profile, most probably the user is deleted. - sessionUser.userfullname = Translate.instant('core.deleteduser') + ' ' + sessionUser.userid; + await this.sessions.reload(); + } catch (error) { + CoreDomUtils.showErrorModalDefault(error, 'core.errorloadingcontent', true); + } finally { + modal.dismiss(); } } @@ -145,11 +119,9 @@ export class AddonModChatSessionsPage implements AfterViewInit, OnDestroy { */ async refreshSessions(refresher: IonRefresher): Promise { try { - await CoreUtils.ignoreErrors(CoreUtils.allPromises([ - CoreGroups.invalidateActivityGroupInfo(this.cmId), - AddonModChat.invalidateSessions(this.chatId, this.groupId, this.showAll), - ])); + this.sessions.getSource().setDirty(true); + await this.sessions.getSource().invalidateCache(); await this.fetchSessions(); } finally { refresher.complete(); @@ -163,7 +135,10 @@ export class AddonModChatSessionsPage implements AfterViewInit, OnDestroy { * @param event The event. */ showMoreUsers(session: AddonModChatSessionFormatted, event: Event): void { - session.sessionusers = session.allsessionusers!; + if (session.allsessionusers) { + session.sessionusers = session.allsessionusers; + } + event.stopPropagation(); } @@ -175,68 +150,3 @@ export class AddonModChatSessionsPage implements AfterViewInit, OnDestroy { } } - -/** - * Helper class to manage sessions. - */ -class AddonChatSessionsManager extends CorePageItemsListManager { - - chatId = -1; - groupId = 0; - - constructor(pageComponent: unknown) { - super(pageComponent); - } - - /** - * Set chat ID. - * - * @param chatId Chat ID. - */ - setChatId(chatId: number): void { - this.chatId = chatId; - } - - /** - * Set group ID. - * - * @param groupId Group ID. - */ - setGroupId(groupId: number): void { - this.groupId = groupId; - } - - /** - * @inheritdoc - */ - protected getItemPath(session: AddonModChatSessionFormatted): string { - return `${session.sessionstart}/${session.sessionend}`; - } - - /** - * @inheritdoc - */ - protected getItemQueryParams(): Params { - return { - chatId: this.chatId, - groupId: this.groupId, - }; - } - -} - -/** - * Fields added to chat session in this view. - */ -type AddonModChatSessionFormatted = Omit & { - duration?: number; // Session duration. - sessionusers: AddonModChatUserSessionFormatted[]; - allsessionusers?: AddonModChatUserSessionFormatted[]; // All session users. -}; - -/** - * Fields added to user session in this view. - */ -type AddonModChatUserSessionFormatted = AddonModChatSessionUser & { - userfullname?: string; // User full name. -}; diff --git a/src/core/classes/items-management/list-items-manager.ts b/src/core/classes/items-management/list-items-manager.ts index 9229b1de4..4fae782f3 100644 --- a/src/core/classes/items-management/list-items-manager.ts +++ b/src/core/classes/items-management/list-items-manager.ts @@ -64,8 +64,10 @@ export class CoreListItemsManager< * * @param splitView Split view component. */ - async start(splitView: CoreSplitViewComponent): Promise { - this.watchSplitViewOutlet(splitView); + async start(splitView?: CoreSplitViewComponent): Promise { + if (splitView) { + this.watchSplitViewOutlet(splitView); + } // Calculate current selected item. this.updateSelectedItem(); @@ -121,7 +123,13 @@ export class CoreListItemsManager< * * @param item Item. */ - async select(item: Item): Promise { + async select(item: Item | null): Promise { + if (!item) { + await this.navigateToIndex({ reset: this.resetNavigation() }); + + return; + } + await this.navigateToItem(item, { reset: this.resetNavigation() }); } @@ -172,17 +180,9 @@ export class CoreListItemsManager< protected updateSelectedItem(route: ActivatedRouteSnapshot | null = null): void { super.updateSelectedItem(route); - if (CoreScreen.isMobile || this.selectedItem !== null || this.splitView?.isNested) { - return; - } + const selectDefault = CoreScreen.isTablet && this.selectedItem === null && this.splitView && !this.splitView.isNested; - const defaultItem = this.getDefaultItem(); - - if (!defaultItem) { - return; - } - - this.select(defaultItem); + this.select(selectDefault ? this.getDefaultItem() : this.selectedItem); } /** diff --git a/src/core/classes/items-management/routed-items-manager.ts b/src/core/classes/items-management/routed-items-manager.ts index 6feb244cc..ac1b608ac 100644 --- a/src/core/classes/items-management/routed-items-manager.ts +++ b/src/core/classes/items-management/routed-items-manager.ts @@ -119,6 +119,34 @@ export abstract class CoreRoutedItemsManager< await CoreNavigator.navigate(pathPrefix + itemPath, { params, ...options }); } + /** + * Navigate to the index page. + * + * @param options Navigation options. + */ + protected async navigateToIndex( + options: Pick = {}, + ): Promise { + // Get current route in the page. + const route = this.getCurrentPageRoute(); + + if (route === null) { + return; + } + + // If the current page is already the index, do nothing. + const selectedItemPath = this.getSelectedItemPath(route.snapshot); + + if (selectedItemPath === null) { + return; + } + + // Navigate to index. + const indexPath = selectedItemPath ? selectedItemPath.split('/').fill('../').join('') : ''; + + await CoreNavigator.navigate(indexPath, options); + } + /** * @inheritdoc */ diff --git a/src/core/classes/page-items-list-manager.ts b/src/core/classes/page-items-list-manager.ts deleted file mode 100644 index 920e7db66..000000000 --- a/src/core/classes/page-items-list-manager.ts +++ /dev/null @@ -1,300 +0,0 @@ -// (C) Copyright 2015 Moodle Pty Ltd. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { ActivatedRoute, ActivatedRouteSnapshot, Params, UrlSegment } from '@angular/router'; -import { Subscription } from 'rxjs'; - -import { CoreSplitViewComponent } from '@components/split-view/split-view'; -import { CoreNavigator } from '@services/navigator'; -import { CoreScreen } from '@services/screen'; -import { CoreUtils } from '@services/utils/utils'; - -/** - * Helper class to manage the state and routing of a list of items in a page, for example on pages using a split view. - * - * @deprecated use CoreListItemsManager instead. - */ -export abstract class CorePageItemsListManager { - - protected itemsList: Item[] | null = null; - protected itemsMap: Record | null = null; - protected hasMoreItems = true; - protected selectedItem: Item | null = null; - protected pageRouteLocator?: unknown | ActivatedRoute; - protected splitView?: CoreSplitViewComponent; - protected splitViewOutletSubscription?: Subscription; - - constructor(pageRouteLocator: unknown | ActivatedRoute) { - this.pageRouteLocator = pageRouteLocator; - } - - get items(): Item[] { - return this.itemsList || []; - } - - get loaded(): boolean { - return this.itemsMap !== null; - } - - get completed(): boolean { - return !this.hasMoreItems; - } - - get empty(): boolean { - return this.itemsList === null || this.itemsList.length === 0; - } - - /** - * Process page started operations. - * - * @param splitView Split view component. - */ - async start(splitView: CoreSplitViewComponent): Promise { - this.watchSplitViewOutlet(splitView); - - // Calculate current selected item. - const route = this.getCurrentPageRoute(); - this.updateSelectedItem(route?.snapshot ?? null); - - // Select default item if none is selected on a non-mobile layout. - if (!CoreScreen.isMobile && this.selectedItem === null && !splitView.isNested) { - const defaultItem = this.getDefaultItem(); - - if (defaultItem) { - this.select(defaultItem); - } - } - - // Log activity. - await CoreUtils.ignoreErrors(this.logActivity()); - } - - /** - * Process page destroyed operations. - */ - destroy(): void { - this.splitViewOutletSubscription?.unsubscribe(); - } - - /** - * Watch a split view outlet to keep track of the selected item. - * - * @param splitView Split view component. - */ - watchSplitViewOutlet(splitView: CoreSplitViewComponent): void { - this.splitView = splitView; - this.splitViewOutletSubscription = splitView.outletRouteObservable.subscribe( - route => this.updateSelectedItem(this.getPageRouteFromSplitViewOutlet(route)), - ); - - this.updateSelectedItem(this.getPageRouteFromSplitViewOutlet(splitView.outletRoute) ?? null); - } - - /** - * Reset items data. - */ - resetItems(): void { - this.itemsList = null; - this.itemsMap = null; - this.hasMoreItems = true; - this.selectedItem = null; - } - - /** - * Check whether the given item is selected or not. - * - * @param item Item. - * @return Whether the given item is selected. - */ - isSelected(item: Item): boolean { - return this.selectedItem === item; - } - - /** - * Return the current aria value. - * - * @param item Item. - * @return Will return the current value of the item if selected, false otherwise. - */ - getItemAriaCurrent(item: Item): string { - return this.isSelected(item) ? 'page' : 'false'; - } - - /** - * Select an item. - * - * @param item Item. - */ - async select(item: Item): Promise { - // Get current route in the page. - const route = this.getCurrentPageRoute(); - - if (route === null) { - return; - } - - // If this item is already selected, do nothing. - const itemPath = this.getItemPath(item); - const selectedItemPath = this.getSelectedItemPath(route.snapshot); - - if (selectedItemPath === itemPath) { - return; - } - - // Navigate to item. - const params = this.getItemQueryParams(item); - const reset = this.resetNavigation(); - const pathPrefix = selectedItemPath ? selectedItemPath.split('/').fill('../').join('') : ''; - - await CoreNavigator.navigate(pathPrefix + itemPath, { params, reset }); - } - - /** - * Set the list of items. - * - * @param items Items. - * @param hasMoreItems Whether the list has more items that haven't been loaded. - */ - setItems(items: Item[], hasMoreItems: boolean = false): void { - this.hasMoreItems = hasMoreItems; - this.itemsList = items.slice(0); - this.itemsMap = items.reduce((map, item) => { - map[this.getItemPath(item)] = item; - - return map; - }, {}); - - this.updateSelectedItem(this.getPageRouteFromSplitViewOutlet(this.splitView?.outletRoute ?? null)); - } - - /** - * Log activity when the page starts. - */ - protected async logActivity(): Promise { - // - } - - /** - * Update the selected item given the current route. - * - * @param route Current route. - */ - protected updateSelectedItem(route: ActivatedRouteSnapshot | null): void { - const selectedItemPath = this.getSelectedItemPath(route); - - this.selectedItem = selectedItemPath - ? this.itemsMap?.[selectedItemPath] ?? null - : null; - } - - /** - * Check whether to reset navigation when selecting an item. - * - * @returns boolean Whether navigation should be reset. - */ - protected resetNavigation(): boolean { - if (!CoreScreen.isTablet) { - return false; - } - - return !!this.splitView && !this.splitView?.isNested; - } - - /** - * Get the item that should be selected by default. - */ - protected getDefaultItem(): Item | null { - return this.itemsList?.[0] || null; - } - - /** - * Get the query parameters to use when navigating to an item page. - * - * @param item Item. - * @return Query parameters to use when navigating to the item page. - */ - // eslint-disable-next-line @typescript-eslint/no-unused-vars - protected getItemQueryParams(item: Item): Params { - return {}; - } - - /** - * Get the path to use when navigating to an item page. - * - * @param item Item. - * @return Path to use when navigating to the item page. - */ - protected abstract getItemPath(item: Item): string; - - /** - * Get the path of the selected item given the current route. - * - * @param route Page route. - * @return Path of the selected item in the given route. - */ - protected getSelectedItemPath(route?: ActivatedRouteSnapshot | null): string | null { - const segments: UrlSegment[] = []; - - while ((route = route?.firstChild)) { - segments.push(...route.url); - } - - return segments.map(segment => segment.path).join('/').replace(/\/+/, '/').trim() || null; - } - - /** - * Get page route. - * - * @returns Current page route, if any. - */ - private getCurrentPageRoute(): ActivatedRoute | null { - if (this.pageRouteLocator instanceof ActivatedRoute) { - return CoreNavigator.isRouteActive(this.pageRouteLocator) ? this.pageRouteLocator : null; - } - - return CoreNavigator.getCurrentRoute({ pageComponent: this.pageRouteLocator }); - } - - /** - * Get the page route given a child route on the splitview outlet. - * - * @param route Child route. - * @return Page route. - */ - private getPageRouteFromSplitViewOutlet(route: ActivatedRouteSnapshot | null): ActivatedRouteSnapshot | null { - const isPageRoute = this.buildRouteMatcher(); - - while (route && !isPageRoute(route)) { - route = route.parent; - } - - return route; - } - - /** - * Build a function to check whether the given snapshot belongs to the page. - * - * @returns Route matcher. - */ - private buildRouteMatcher(): (route: ActivatedRouteSnapshot) => boolean { - if (this.pageRouteLocator instanceof ActivatedRoute) { - const pageRoutePath = CoreNavigator.getRouteFullPath(this.pageRouteLocator.snapshot); - - return route => CoreNavigator.getRouteFullPath(route) === pageRoutePath; - } - - return route => route.component === this.pageRouteLocator; - } - -} diff --git a/src/core/features/grades/classes/grades-courses-source.ts b/src/core/features/grades/classes/grades-courses-source.ts new file mode 100644 index 000000000..6d2b74608 --- /dev/null +++ b/src/core/features/grades/classes/grades-courses-source.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 { CoreRoutedItemsManagerSource } from '@classes/items-management/routed-items-manager-source'; +import { CoreGrades } from '../services/grades'; +import { CoreGradesGradeOverviewWithCourseData, CoreGradesHelper } from '../services/grades-helper'; + +/** + * Provides a collection of courses. + */ +export class CoreGradesCoursesSource extends CoreRoutedItemsManagerSource { + + /** + * @inheritdoc + */ + protected async loadPageItems(): Promise<{ items: CoreGradesGradeOverviewWithCourseData[] }> { + const grades = await CoreGrades.getCoursesGrades(); + const courses = await CoreGradesHelper.getGradesCourseData(grades); + + return { items: courses }; + } + + /** + * @inheritdoc + */ + getItemPath(course: CoreGradesGradeOverviewWithCourseData): string { + return course.courseid.toString(); + } + +} diff --git a/src/core/features/grades/grades-course-lazy.module.ts b/src/core/features/grades/grades-course-lazy.module.ts index e6164e920..974a15c9d 100644 --- a/src/core/features/grades/grades-course-lazy.module.ts +++ b/src/core/features/grades/grades-course-lazy.module.ts @@ -22,10 +22,7 @@ const routes: Routes = [ { path: '', component: CoreGradesCoursePage, - data: { - useSplitView: false, - outsideGradesTab: true, - }, + data: { swipeEnabled: false }, }, ]; diff --git a/src/core/features/grades/grades-lazy.module.ts b/src/core/features/grades/grades-courses-lazy.module.ts similarity index 72% rename from src/core/features/grades/grades-lazy.module.ts rename to src/core/features/grades/grades-courses-lazy.module.ts index d825a2621..8dbc73cf5 100644 --- a/src/core/features/grades/grades-lazy.module.ts +++ b/src/core/features/grades/grades-courses-lazy.module.ts @@ -22,33 +22,21 @@ 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.page'; -import { CoreGradesGradePage } from './pages/grade/grade.page'; -import { CoreGradesUserHandlerService } from './services/handlers/user'; const mobileRoutes: Routes = [ { path: '', - data: { - mainMenuTabRoot: CoreGradesUserHandlerService.PAGE_NAME, - }, component: CoreGradesCoursesPage, }, { path: ':courseId', component: CoreGradesCoursePage, }, - { - path: ':courseId/:gradeId', - component: CoreGradesGradePage, - }, ]; const tabletRoutes: Routes = [ { path: '', - data: { - mainMenuTabRoot: CoreGradesUserHandlerService.PAGE_NAME, - }, component: CoreGradesCoursesPage, children: [ { @@ -57,16 +45,6 @@ const tabletRoutes: Routes = [ }, ], }, - { - path: ':courseId', - component: CoreGradesCoursePage, - children: [ - { - path: ':gradeId', - component: CoreGradesGradePage, - }, - ], - }, ]; const routes: Routes = [ @@ -82,7 +60,6 @@ const routes: Routes = [ ], declarations: [ CoreGradesCoursesPage, - CoreGradesGradePage, ], }) -export class CoreGradesLazyModule {} +export class CoreGradesCoursesLazyModule {} diff --git a/src/core/features/grades/grades.module.ts b/src/core/features/grades/grades.module.ts index d7ca58cf3..6669a9161 100644 --- a/src/core/features/grades/grades.module.ts +++ b/src/core/features/grades/grades.module.ts @@ -15,16 +15,17 @@ import { APP_INITIALIZER, NgModule, Type } from '@angular/core'; import { Routes } from '@angular/router'; import { CoreContentLinksDelegate } from '@features/contentlinks/services/contentlinks-delegate'; +import { COURSE_PAGE_NAME } from '@features/course/course.module'; import { CoreCourseIndexRoutingModule } from '@features/course/pages/index/index-routing.module'; import { CoreCourseOptionsDelegate } from '@features/course/services/course-options-delegate'; -import { CoreMainMenuRoutingModule } from '@features/mainmenu/mainmenu-routing.module'; import { CoreMainMenuTabRoutingModule } from '@features/mainmenu/mainmenu-tab-routing.module'; import { CoreUserDelegate } from '@features/user/services/user-delegate'; +import { PARTICIPANTS_PAGE_NAME } from '@features/user/user.module'; import { CoreGradesProvider } from './services/grades'; import { CoreGradesHelperProvider } from './services/grades-helper'; import { CoreGradesCourseOptionHandler } from './services/handlers/course-option'; import { CoreGradesOverviewLinkHandler } from './services/handlers/overview-link'; -import { CoreGradesUserHandler, CoreGradesUserHandlerService } from './services/handlers/user'; +import { CoreGradesUserHandler } from './services/handlers/user'; import { CoreGradesUserLinkHandler } from './services/handlers/user-link'; export const CORE_GRADES_SERVICES: Type[] = [ @@ -32,28 +33,29 @@ export const CORE_GRADES_SERVICES: Type[] = [ CoreGradesHelperProvider, ]; -const routes: Routes = [ +export const GRADES_PAGE_NAME = 'grades'; + +const mainMenuChildrenRoutes: Routes = [ { - path: CoreGradesUserHandlerService.PAGE_NAME, - loadChildren: () => import('@features/grades/grades-lazy.module').then(m => m.CoreGradesLazyModule), + path: GRADES_PAGE_NAME, + loadChildren: () => import('./grades-courses-lazy.module').then(m => m.CoreGradesCoursesLazyModule), }, { - path: 'user-grades/:courseId', - loadChildren: () => import('@features/grades/grades-course-lazy.module').then(m => m.CoreGradesCourseLazyModule), + path: `${COURSE_PAGE_NAME}/:courseId/${PARTICIPANTS_PAGE_NAME}/:userId/${GRADES_PAGE_NAME}`, + loadChildren: () => import('./grades-course-lazy.module').then(m => m.CoreGradesCourseLazyModule), }, ]; const courseIndexRoutes: Routes = [ { - path: 'grades', - loadChildren: () => import('@features/grades/grades-course-lazy.module').then(m => m.CoreGradesCourseLazyModule), + path: GRADES_PAGE_NAME, + loadChildren: () => import('./grades-course-lazy.module').then(m => m.CoreGradesCourseLazyModule), }, ]; @NgModule({ imports: [ - CoreMainMenuTabRoutingModule.forChild(routes), - CoreMainMenuRoutingModule.forChild({ children: routes }), + CoreMainMenuTabRoutingModule.forChild(mainMenuChildrenRoutes), CoreCourseIndexRoutingModule.forChild({ children: courseIndexRoutes }), ], providers: [ diff --git a/src/core/features/grades/pages/course/course.html b/src/core/features/grades/pages/course/course.html index b4e94d4d0..e5e717bbe 100644 --- a/src/core/features/grades/pages/course/course.html +++ b/src/core/features/grades/pages/course/course.html @@ -4,36 +4,41 @@ -

{{ 'core.grades.grades' | translate }}

+

{{ title }}

- - - - - - - - -
- - - - - - - - + + + + + + + +
+
- {{ 'core.grades.' + column.name | translate }} -
+ + + + + + + + - - + - - + [class.ion-hide-md-down]="column.hiddenPhone" [innerHTML]="row[column.name]"> + + - -
+ {{ 'core.grades.' + column.name | translate }} +
+ + + + + + +
-
-
-
+ + + + + + + + + + +

+ + +

+
+
+ + + + + + + + +

+ + +

+
+
+ + + +

{{ 'core.grades.weight' | translate}}

+

+
+
+ + + +

{{ 'core.grades.grade' | translate}}

+

+
+
+ + + +

{{ 'core.grades.range' | translate}}

+

+
+
+ + + +

{{ 'core.grades.percentage' | translate}}

+

+
+
+ + + +

{{ 'core.grades.lettergrade' | translate}}

+

+
+
+ + + +

{{ 'core.grades.rank' | translate}}

+

+
+
+ + + +

{{ 'core.grades.average' | translate}}

+

+
+
+ + + +

{{ 'core.grades.feedback' | translate}}

+

+ + +

+
+
+ + + +

{{ 'core.grades.contributiontocoursetotal' | translate}}

+

+
+
+
+ + +
+ + +
+ diff --git a/src/core/features/grades/pages/course/course.page.ts b/src/core/features/grades/pages/course/course.page.ts index 224d9369b..fe22fc527 100644 --- a/src/core/features/grades/pages/course/course.page.ts +++ b/src/core/features/grades/pages/course/course.page.ts @@ -12,23 +12,25 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { ActivatedRoute, Params } from '@angular/router'; -import { AfterViewInit, Component, OnDestroy, ViewChild } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { AfterViewInit, Component, ElementRef, OnDestroy } from '@angular/core'; import { IonRefresher } from '@ionic/angular'; import { CoreDomUtils } from '@services/utils/dom'; import { CoreGrades } from '@features/grades/services/grades'; import { - CoreGradesFormattedTable, CoreGradesFormattedTableColumn, CoreGradesFormattedTableRow, CoreGradesHelper, } from '@features/grades/services/grades-helper'; import { CoreSites } from '@services/sites'; import { CoreUtils } from '@services/utils/utils'; -import { CoreSplitViewComponent, CoreSplitViewMode } from '@components/split-view/split-view'; -import { CorePageItemsListManager } from '@classes/page-items-list-manager'; import { CoreNavigator } from '@services/navigator'; +import { CoreScreen } from '@services/screen'; +import { Translate } from '@singletons'; +import { CoreSwipeNavigationItemsManager } from '@classes/items-management/swipe-navigation-items-manager'; +import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker'; +import { CoreGradesCoursesSource } from '@features/grades/classes/grades-courses-source'; /** * Page that displays a course grades. @@ -40,18 +42,29 @@ import { CoreNavigator } from '@services/navigator'; }) export class CoreGradesCoursePage implements AfterViewInit, OnDestroy { - grades!: CoreGradesCourseManager; - splitViewMode?: CoreSplitViewMode; - - @ViewChild(CoreSplitViewComponent) splitView!: CoreSplitViewComponent; - - constructor(protected route: ActivatedRoute) { - let courseId: number; - let userId: number; + courseId!: number; + userId!: number; + expandLabel!: string; + collapseLabel!: string; + title?: string; + courses?: CoreSwipeNavigationItemsManager; + columns?: CoreGradesFormattedTableColumn[]; + rows?: CoreGradesFormattedTableRow[]; + totalColumnsSpan?: number; + withinSplitView?: boolean; + constructor(protected route: ActivatedRoute, protected element: ElementRef) { try { - courseId = CoreNavigator.getRequiredRouteNumberParam('courseId', { route }); - userId = CoreNavigator.getRouteNumberParam('userId', { route }) ?? CoreSites.getCurrentSiteUserId(); + this.courseId = CoreNavigator.getRequiredRouteNumberParam('courseId', { route }); + this.userId = CoreNavigator.getRouteNumberParam('userId', { route }) ?? CoreSites.getCurrentSiteUserId(); + this.expandLabel = Translate.instant('core.expand'); + this.collapseLabel = Translate.instant('core.collapse'); + + if (route.snapshot.data.swipeEnabled ?? true) { + const source = CoreRoutedItemsManagerSourcesTracker.getOrCreateSource(CoreGradesCoursesSource, []); + + this.courses = new CoreSwipeNavigationItemsManager(source); + } } catch (error) { CoreDomUtils.showErrorModal(error); @@ -59,28 +72,69 @@ export class CoreGradesCoursePage implements AfterViewInit, OnDestroy { return; } + } - const useSplitView = route.snapshot.data.useSplitView ?? true; - const outsideGradesTab = route.snapshot.data.outsideGradesTab ?? false; - - this.splitViewMode = useSplitView ? undefined : CoreSplitViewMode.MENU_ONLY; - this.grades = new CoreGradesCourseManager(CoreGradesCoursePage, courseId, userId, outsideGradesTab); + get showSummary(): boolean { + return CoreScreen.isMobile || !!this.withinSplitView; } /** * @inheritdoc */ async ngAfterViewInit(): Promise { - await this.fetchInitialGrades(); + this.withinSplitView = !!this.element.nativeElement.parentElement?.closest('core-split-view'); - this.grades.start(this.splitView); + await this.courses?.start(); + await this.fetchInitialGrades(); + await CoreGrades.logCourseGradesView(this.courseId, this.userId); } /** * @inheritdoc */ ngOnDestroy(): void { - this.grades.destroy(); + this.courses?.destroy(); + } + + /** + * Get aria label for row. + * + * @param row Row. + * @returns Aria label, if applicable. + */ + rowAriaLabel(row: CoreGradesFormattedTableRow): string | undefined { + if (!row.expandable || !this.showSummary) { + return; + } + + const actionLabel = row.expanded ? this.collapseLabel : this.expandLabel; + + return `${actionLabel} ${row.ariaLabel}`; + } + + /** + * Toggle whether a row is expanded or collapsed. + * + * @param row Row. + */ + toggleRow(row: CoreGradesFormattedTableRow): void { + if (!this.rows || !this.columns) { + return; + } + + row.expanded = !row.expanded; + + let colspan: number = this.columns.length + (row.colspan ?? 0) - 1; + for (let i = this.rows.indexOf(row) - 1; i >= 0; i--) { + const previousRow = this.rows[i]; + + if (previousRow.expandable || !previousRow.colspan || !previousRow.rowspan || previousRow.colspan !== colspan) { + continue; + } + + colspan++; + previousRow.rowspan += row.expanded ? 1 : -1; + } } /** @@ -89,9 +143,7 @@ export class CoreGradesCoursePage implements AfterViewInit, OnDestroy { * @param refresher Refresher. */ async refreshGrades(refresher: IonRefresher): Promise { - const { courseId, userId } = this.grades; - - await CoreUtils.ignoreErrors(CoreGrades.invalidateCourseGradesData(courseId, userId)); + await CoreUtils.ignoreErrors(CoreGrades.invalidateCourseGradesData(this.courseId, this.userId)); await CoreUtils.ignoreErrors(this.fetchGrades()); refresher?.complete(); @@ -106,7 +158,8 @@ export class CoreGradesCoursePage implements AfterViewInit, OnDestroy { } catch (error) { CoreDomUtils.showErrorModalDefault(error, 'Error loading course'); - this.grades.setTable({ columns: [], rows: [] }); + this.columns = []; + this.rows = []; } } @@ -114,99 +167,13 @@ export class CoreGradesCoursePage implements AfterViewInit, OnDestroy { * Update the table of grades. */ private async fetchGrades(): Promise { - const table = await CoreGrades.getCourseGradesTable(this.grades.courseId, this.grades.userId); + const table = await CoreGrades.getCourseGradesTable(this.courseId, this.userId); const formattedTable = await CoreGradesHelper.formatGradesTable(table); - this.grades.setTable(formattedTable); + this.title = formattedTable.rows[0]?.gradeitem ?? Translate.instant('core.grades.grades'); + this.columns = formattedTable.columns; + this.rows = formattedTable.rows; + this.totalColumnsSpan = formattedTable.columns.reduce((total, column) => total + column.colspan, 0); } } - -/** - * Helper to manage the table of grades. - */ -class CoreGradesCourseManager extends CorePageItemsListManager { - - courseId: number; - userId: number; - columns?: CoreGradesFormattedTableColumn[]; - rows?: CoreGradesFormattedTableRow[]; - - private outsideGradesTab: boolean; - - constructor(pageComponent: unknown, courseId: number, userId: number, outsideGradesTab: boolean) { - super(pageComponent); - - this.courseId = courseId; - this.userId = userId; - this.outsideGradesTab = outsideGradesTab; - } - - /** - * Set grades table. - * - * @param table Grades table. - */ - setTable(table: CoreGradesFormattedTable): void { - this.columns = table.columns; - this.rows = table.rows; - - this.setItems(table.rows.filter(this.isFilledRow)); - } - - /** - * @inheritdoc - */ - async select(row: CoreGradesFormattedTableRowFilled): Promise { - if (this.outsideGradesTab) { - await CoreNavigator.navigateToSitePath(`/grades/${this.courseId}/${row.id}`); - - return; - } - - return super.select(row); - } - - /** - * @inheritdoc - */ - protected getDefaultItem(): CoreGradesFormattedTableRowFilled | null { - return null; - } - - /** - * @inheritdoc - */ - protected getItemPath(row: CoreGradesFormattedTableRowFilled): string { - return row.id.toString(); - } - - /** - * @inheritdoc - */ - protected getItemQueryParams(): Params { - return { userId: this.userId }; - } - - /** - * @inheritdoc - */ - protected async logActivity(): Promise { - await CoreGrades.logCourseGradesView(this.courseId, this.userId); - } - - /** - * Check whether the given row is filled or not. - * - * @param row Grades table row. - * @return Whether the given row is filled or not. - */ - private isFilledRow(row: CoreGradesFormattedTableRow): row is CoreGradesFormattedTableRowFilled { - return 'id' in row; - } - -} - -export type CoreGradesFormattedTableRowFilled = Omit & { - id: number; -}; diff --git a/src/core/features/grades/pages/course/course.scss b/src/core/features/grades/pages/course/course.scss index 2f0239c09..3a004d4a6 100644 --- a/src/core/features/grades/pages/course/course.scss +++ b/src/core/features/grades/pages/course/course.scss @@ -61,11 +61,15 @@ background-color: var(--header-background); } + thead #gradeitem { + @include padding(null, null, null, 23px); + } + tbody th { font-weight: normal; } - #gradeitem { + tbody #gradeitem { @include padding(null, null, null, 5px); } @@ -131,12 +135,15 @@ } } -} + ion-list, ion-item::part(native) { + background-color: transparent; + } + + &.summary .ion-hide-md-down { + display: none; + opacity: 0; + } -core-split-view.nested .core-grades-table .ion-hide-md-down, -core-split-view.menu-and-content .core-grades-table .ion-hide-md-down { - display: none; - opacity: 0; } @include media-breakpoint-down(md) { diff --git a/src/core/features/grades/pages/courses/courses.page.ts b/src/core/features/grades/pages/courses/courses.page.ts index bd6a4c51d..dd47d084c 100644 --- a/src/core/features/grades/pages/courses/courses.page.ts +++ b/src/core/features/grades/pages/courses/courses.page.ts @@ -13,11 +13,12 @@ // limitations under the License. import { AfterViewInit, Component, OnDestroy, ViewChild } from '@angular/core'; -import { CorePageItemsListManager } from '@classes/page-items-list-manager'; +import { CoreListItemsManager } from '@classes/items-management/list-items-manager'; +import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker'; import { CoreSplitViewComponent } from '@components/split-view/split-view'; +import { CoreGradesCoursesSource } from '@features/grades/classes/grades-courses-source'; import { CoreGrades } from '@features/grades/services/grades'; -import { CoreGradesGradeOverviewWithCourseData, CoreGradesHelper } from '@features/grades/services/grades-helper'; import { IonRefresher } from '@ionic/angular'; import { CoreDomUtils } from '@services/utils/dom'; import { CoreUtils } from '@services/utils/utils'; @@ -31,10 +32,16 @@ import { CoreUtils } from '@services/utils/utils'; }) export class CoreGradesCoursesPage implements OnDestroy, AfterViewInit { - courses: CoreGradesCoursesManager = new CoreGradesCoursesManager(CoreGradesCoursesPage); + courses: CoreGradesCoursesManager; @ViewChild(CoreSplitViewComponent) splitView!: CoreSplitViewComponent; + constructor() { + const source = CoreRoutedItemsManagerSourcesTracker.getOrCreateSource(CoreGradesCoursesSource, []); + + this.courses = new CoreGradesCoursesManager(source, CoreGradesCoursesPage); + } + /** * @inheritdoc */ @@ -58,7 +65,7 @@ export class CoreGradesCoursesPage implements OnDestroy, AfterViewInit { */ async refreshCourses(refresher: IonRefresher): Promise { await CoreUtils.ignoreErrors(CoreGrades.invalidateCoursesGradesData()); - await CoreUtils.ignoreErrors(this.fetchCourses()); + await CoreUtils.ignoreErrors(this.courses.reload()); refresher?.complete(); } @@ -68,37 +75,18 @@ export class CoreGradesCoursesPage implements OnDestroy, AfterViewInit { */ private async fetchInitialCourses(): Promise { try { - await this.fetchCourses(); + await this.courses.load(); } catch (error) { CoreDomUtils.showErrorModalDefault(error, 'Error loading courses'); - - this.courses.setItems([]); } } - /** - * Update the list of courses. - */ - private async fetchCourses(): Promise { - const grades = await CoreGrades.getCoursesGrades(); - const courses = await CoreGradesHelper.getGradesCourseData(grades); - - this.courses.setItems(courses); - } - } /** * Helper class to manage courses. */ -class CoreGradesCoursesManager extends CorePageItemsListManager { - - /** - * @inheritdoc - */ - protected getItemPath(courseGrade: CoreGradesGradeOverviewWithCourseData): string { - return courseGrade.courseid.toString(); - } +class CoreGradesCoursesManager extends CoreListItemsManager { /** * @inheritdoc diff --git a/src/core/features/grades/pages/grade/grade.html b/src/core/features/grades/pages/grade/grade.html deleted file mode 100644 index ddb6bf6d2..000000000 --- a/src/core/features/grades/pages/grade/grade.html +++ /dev/null @@ -1,112 +0,0 @@ - - - - - - -

{{ 'core.grades.grade' | translate }}

-
-
-
- - - - - - - - - - - - - - -

- - -

-
-
- - - - - - - -

- - -

-
-
- - - -

{{ 'core.grades.weight' | translate}}

-

-
-
- - - -

{{ 'core.grades.grade' | translate}}

-

-
-
- - - -

{{ 'core.grades.range' | translate}}

-

-
-
- - - -

{{ 'core.grades.percentage' | translate}}

-

-
-
- - - -

{{ 'core.grades.lettergrade' | translate}}

-

-
-
- - - -

{{ 'core.grades.rank' | translate}}

-

-
-
- - - -

{{ 'core.grades.average' | translate}}

-

-
-
- - - -

{{ 'core.grades.feedback' | translate}}

-

- - -

-
-
- - - -

{{ 'core.grades.contributiontocoursetotal' | translate}}

-

-
-
-
-
-
diff --git a/src/core/features/grades/pages/grade/grade.page.ts b/src/core/features/grades/pages/grade/grade.page.ts deleted file mode 100644 index b77e08ce6..000000000 --- a/src/core/features/grades/pages/grade/grade.page.ts +++ /dev/null @@ -1,85 +0,0 @@ -// (C) Copyright 2015 Moodle Pty Ltd. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { Component, OnInit } from '@angular/core'; -import { IonRefresher } from '@ionic/angular'; - -import { CoreDomUtils } from '@services/utils/dom'; -import { CoreGrades } from '@features/grades/services/grades'; -import { CoreGradesFormattedRow, CoreGradesHelper } from '@features/grades/services/grades-helper'; -import { CoreSites } from '@services/sites'; -import { CoreUtils } from '@services/utils/utils'; -import { CoreNavigator } from '@services/navigator'; - -/** - * Page that displays activity grade. - */ -@Component({ - selector: 'page-core-grades-grade', - templateUrl: 'grade.html', -}) -export class CoreGradesGradePage implements OnInit { - - courseId!: number; - userId!: number; - gradeId!: number; - grade?: CoreGradesFormattedRow | null; - gradeLoaded = false; - - constructor() { - try { - this.courseId = CoreNavigator.getRequiredRouteNumberParam('courseId'); - this.gradeId = CoreNavigator.getRequiredRouteNumberParam('gradeId'); - this.userId = CoreNavigator.getRouteNumberParam('userId') ?? CoreSites.getCurrentSiteUserId(); - } catch (error) { - CoreDomUtils.showErrorModal(error); - - CoreNavigator.back(); - - return; - } - } - - /** - * @inheritdoc - */ - ngOnInit(): void { - this.fetchGrade(); - } - - /** - * Fetch all the data required for the view. - */ - async fetchGrade(): Promise { - try { - this.grade = await CoreGradesHelper.getGradeItem(this.courseId, this.gradeId, this.userId); - this.gradeLoaded = true; - } catch (error) { - CoreDomUtils.showErrorModalDefault(error, 'Error loading grade item'); - } - } - - /** - * Refresh data. - * - * @param refresher Refresher. - */ - async refreshGrade(refresher: IonRefresher): Promise { - await CoreUtils.ignoreErrors(CoreGrades.invalidateCourseGradesData(this.courseId, this.userId)); - await CoreUtils.ignoreErrors(this.fetchGrade()); - - refresher.complete(); - } - -} diff --git a/src/core/features/grades/services/grades-helper.ts b/src/core/features/grades/services/grades-helper.ts index b602a8df5..91af997c6 100644 --- a/src/core/features/grades/services/grades-helper.ts +++ b/src/core/features/grades/services/grades-helper.ts @@ -35,6 +35,7 @@ import { CoreNavigator } from '@services/navigator'; import { makeSingleton, Translate } from '@singletons'; import { CoreError } from '@classes/errors/error'; import { CoreCourseHelper } from '@features/course/services/course-helper'; +import { GRADES_PAGE_NAME } from '../grades.module'; /** * Service that provides some features regarding grades information. @@ -53,6 +54,7 @@ export class CoreGradesHelperProvider { * * @param tableRow JSON object representing row of grades table data. * @return Formatted row object. + * @deprecated since app 4.0 */ protected async formatGradeRow(tableRow: CoreGradesTableRow): Promise { const row: CoreGradesFormattedRow = { @@ -125,6 +127,13 @@ export class CoreGradesHelperProvider { content = CoreTextUtils.replaceNewLines(content, '
'); } + if (row.itemtype !== 'category') { + row.expandable = true; + row.expanded = false; + row.detailsid = `grade-item-${row.id}-details`; + row.ariaLabel = `${row.gradeitem} (${row.grade})`; + } + if (content == ' ') { content = ''; } @@ -279,6 +288,7 @@ export class CoreGradesHelperProvider { * @param siteId Site ID. If not defined, current site. * @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down). * @return Promise to be resolved when the grades are retrieved. + * @deprecated since app 4.0 */ async getGradeItem( courseId: number, @@ -381,6 +391,7 @@ export class CoreGradesHelperProvider { * @param table JSON object representing a table with data. * @param gradeId Grade Object identifier. * @return Formatted HTML table. + * @deprecated since app 4.0 */ async getGradesTableRow(table: CoreGradesTable, gradeId: number): Promise { if (table.tabledata) { @@ -472,10 +483,7 @@ export class CoreGradesHelperProvider { const gradeId = item.id; await CoreUtils.ignoreErrors( - CoreNavigator.navigateToSitePath(`/grades/${courseId}/${gradeId}`, { - siteId, - params: { userId }, - }), + CoreNavigator.navigateToSitePath(`/${GRADES_PAGE_NAME}/${courseId}/${gradeId}`, { siteId }), ); } catch (error) { try { @@ -483,10 +491,7 @@ export class CoreGradesHelperProvider { if (userId && userId != currentUserId) { // View another user grades. Open the grades page directly. await CoreUtils.ignoreErrors( - CoreNavigator.navigateToSitePath(`/grades/${courseId}`, { - siteId, - params: { userId }, - }), + CoreNavigator.navigateToSitePath(`/${GRADES_PAGE_NAME}/${courseId}`, { siteId }), ); } @@ -502,7 +507,7 @@ export class CoreGradesHelperProvider { await CoreCourseHelper.getAndOpenCourse(courseId, { selectedTab: 'CoreGrades' }, siteId); } catch (error) { // Cannot get course for some reason, just open the grades page. - await CoreNavigator.navigateToSitePath(`/grades/${courseId}`, { siteId }); + await CoreNavigator.navigateToSitePath(`/${GRADES_PAGE_NAME}/${courseId}`, { siteId }); } } finally { modal.dismiss(); @@ -710,8 +715,12 @@ export type CoreGradesFormattedTable = { export type CoreGradesFormattedTableRow = CoreGradesFormattedRowCommonData & { id?: number; + detailsid?: string; colspan?: number; gradeitem?: string; // The item returned data. + ariaLabel?: string; + expandable?: boolean; + expanded?: boolean; }; export type CoreGradesFormattedTableColumn = { diff --git a/src/core/features/grades/services/handlers/overview-link.ts b/src/core/features/grades/services/handlers/overview-link.ts index 307124802..241e2fc19 100644 --- a/src/core/features/grades/services/handlers/overview-link.ts +++ b/src/core/features/grades/services/handlers/overview-link.ts @@ -15,6 +15,7 @@ import { Injectable } from '@angular/core'; import { CoreContentLinksHandlerBase } from '@features/contentlinks/classes/base-handler'; import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate'; +import { GRADES_PAGE_NAME } from '@features/grades/grades.module'; import { CoreNavigator } from '@services/navigator'; import { makeSingleton } from '@singletons'; import { CoreGrades } from '../grades'; @@ -36,10 +37,7 @@ export class CoreGradesOverviewLinkHandlerService extends CoreContentLinksHandle getActions(): CoreContentLinksAction[] | Promise { return [{ action: siteId => { - CoreNavigator.navigateToSitePath('/grades', { - siteId, - preferCurrentTab: false, - }); + CoreNavigator.navigateToSitePath(GRADES_PAGE_NAME, { siteId }); }, }]; } diff --git a/src/core/features/grades/services/handlers/user.ts b/src/core/features/grades/services/handlers/user.ts index fab0752f6..6c4a655a0 100644 --- a/src/core/features/grades/services/handlers/user.ts +++ b/src/core/features/grades/services/handlers/user.ts @@ -13,6 +13,8 @@ // limitations under the License. import { Injectable } from '@angular/core'; +import { COURSE_PAGE_NAME } from '@features/course/course.module'; +import { GRADES_PAGE_NAME } from '@features/grades/grades.module'; import { CoreGrades } from '@features/grades/services/grades'; import { CoreUserProfile } from '@features/user/services/user'; @@ -21,6 +23,7 @@ import { CoreUserProfileHandler, CoreUserProfileHandlerData, } from '@features/user/services/user-delegate'; +import { PARTICIPANTS_PAGE_NAME } from '@features/user/user.module'; import { CoreNavigator } from '@services/navigator'; import { CoreSites } from '@services/sites'; import { CoreUtils } from '@services/utils/utils'; @@ -32,8 +35,6 @@ import { makeSingleton } from '@singletons'; @Injectable({ providedIn: 'root' }) export class CoreGradesUserHandlerService implements CoreUserProfileHandler { - static readonly PAGE_NAME = 'grades'; - name = 'CoreGrades:viewGrades'; priority = 400; type = CoreUserDelegateService.TYPE_NEW_PAGE; @@ -81,9 +82,9 @@ export class CoreGradesUserHandlerService implements CoreUserProfileHandler { action: (event, user, courseId): void => { event.preventDefault(); event.stopPropagation(); - CoreNavigator.navigateToSitePath(`/user-grades/${courseId}`, { - params: { userId: user.id }, - }); + CoreNavigator.navigateToSitePath( + [COURSE_PAGE_NAME, courseId, PARTICIPANTS_PAGE_NAME, user.id, GRADES_PAGE_NAME].join('/'), + ); }, }; } else { @@ -94,7 +95,7 @@ export class CoreGradesUserHandlerService implements CoreUserProfileHandler { action: (event): void => { event.preventDefault(); event.stopPropagation(); - CoreNavigator.navigateToSitePath(CoreGradesUserHandlerService.PAGE_NAME); + CoreNavigator.navigateToSitePath(GRADES_PAGE_NAME); }, }; } diff --git a/src/core/features/settings/classes/settings-handlers-source.ts b/src/core/features/settings/classes/settings-handlers-source.ts new file mode 100644 index 000000000..30db670ce --- /dev/null +++ b/src/core/features/settings/classes/settings-handlers-source.ts @@ -0,0 +1,45 @@ +// (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 { Params } from '@angular/router'; +import { CoreRoutedItemsManagerSource } from '@classes/items-management/routed-items-manager-source'; +import { CoreSettingsDelegate, CoreSettingsHandlerToDisplay } from '../services/settings-delegate'; + +/** + * Provides a collection of site settings. + */ +export class CoreSettingsHandlersSource extends CoreRoutedItemsManagerSource { + + /** + * @inheritdoc + */ + protected async loadPageItems(): Promise<{ items: CoreSettingsHandlerToDisplay[] }> { + return { items: CoreSettingsDelegate.getHandlers() }; + } + + /** + * @inheritdoc + */ + getItemPath(handler: CoreSettingsHandlerToDisplay): string { + return handler.page; + } + + /** + * @inheritdoc + */ + getItemQueryParams(handler: CoreSettingsHandlerToDisplay): Params { + return handler.params || {}; + } + +} diff --git a/src/core/features/settings/classes/settings-sections-source.ts b/src/core/features/settings/classes/settings-sections-source.ts new file mode 100644 index 000000000..d3bcd02c9 --- /dev/null +++ b/src/core/features/settings/classes/settings-sections-source.ts @@ -0,0 +1,90 @@ +// (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 { CoreConstants } from '@/core/constants'; +import { Params } from '@angular/router'; +import { CoreRoutedItemsManagerSource } from '@classes/items-management/routed-items-manager-source'; +import { SHAREDFILES_PAGE_NAME } from '@features/sharedfiles/sharedfiles.module'; +import { CoreApp } from '@services/app'; + +/** + * Provides a collection of setting sections. + */ +export class CoreSettingsSectionsSource extends CoreRoutedItemsManagerSource { + + /** + * @inheritdoc + */ + protected async loadPageItems(): Promise<{ items: CoreSettingsSection[] }> { + const sections: CoreSettingsSection[] = [ + { + name: 'core.settings.general', + path: 'general', + icon: 'fas-wrench', + }, + { + name: 'core.settings.spaceusage', + path: 'spaceusage', + icon: 'fas-tasks', + }, + { + name: 'core.settings.synchronization', + path: 'sync', + icon: CoreConstants.ICON_SYNC, + }, + ]; + + if (CoreApp.isIOS()) { + sections.push({ + name: 'core.sharedfiles.sharedfiles', + path: SHAREDFILES_PAGE_NAME + '/list/root', + icon: 'fas-folder', + params: { manage: true }, + }); + } + + sections.push({ + name: 'core.settings.about', + path: 'about', + icon: 'fas-id-card', + }); + + return { items: sections }; + } + + /** + * @inheritdoc + */ + getItemPath(section: CoreSettingsSection): string { + return section.path; + } + + /** + * @inheritdoc + */ + getItemQueryParams(section: CoreSettingsSection): Params { + return section.params || {}; + } + +} + +/** + * Settings section. + */ +export type CoreSettingsSection = { + name: string; + path: string; + icon: string; + params?: Params; +}; diff --git a/src/core/features/settings/pages/index/index.ts b/src/core/features/settings/pages/index/index.ts index 6e1653977..9e044dfe3 100644 --- a/src/core/features/settings/pages/index/index.ts +++ b/src/core/features/settings/pages/index/index.ts @@ -13,13 +13,11 @@ // limitations under the License. import { AfterViewInit, Component, OnDestroy, ViewChild } from '@angular/core'; -import { Params } from '@angular/router'; -import { CorePageItemsListManager } from '@classes/page-items-list-manager'; import { CoreSplitViewComponent } from '@components/split-view/split-view'; -import { CoreConstants } from '@/core/constants'; -import { SHAREDFILES_PAGE_NAME } from '@features/sharedfiles/sharedfiles.module'; -import { CoreApp } from '@services/app'; +import { CoreListItemsManager } from '@classes/items-management/list-items-manager'; +import { CoreSettingsSection, CoreSettingsSectionsSource } from '@features/settings/classes/settings-sections-source'; +import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker'; @Component({ selector: 'page-core-settings-index', @@ -27,16 +25,22 @@ import { CoreApp } from '@services/app'; }) export class CoreSettingsIndexPage implements AfterViewInit, OnDestroy { - sections: CoreSettingsSectionsManager = new CoreSettingsSectionsManager(CoreSettingsIndexPage); + sections: CoreListItemsManager; @ViewChild(CoreSplitViewComponent) splitView!: CoreSplitViewComponent; + constructor() { + const source = CoreRoutedItemsManagerSourcesTracker.getOrCreateSource(CoreSettingsSectionsSource, []); + + this.sections = new CoreListItemsManager(source, CoreSettingsIndexPage); + } + /** * @inheritdoc */ - ngAfterViewInit(): void { - this.sections.setItems(this.getSections()); - this.sections.start(this.splitView); + async ngAfterViewInit(): Promise { + await this.sections.load(); + await this.sections.start(this.splitView); } /** @@ -46,77 +50,4 @@ export class CoreSettingsIndexPage implements AfterViewInit, OnDestroy { this.sections.destroy(); } - /** - * Get the sections. - * - * @returns Sections. - */ - protected getSections(): CoreSettingsSection[] { - const sections: CoreSettingsSection[] = [ - { - name: 'core.settings.general', - path: 'general', - icon: 'fas-wrench', - }, - { - name: 'core.settings.spaceusage', - path: 'spaceusage', - icon: 'fas-tasks', - }, - { - name: 'core.settings.synchronization', - path: 'sync', - icon: CoreConstants.ICON_SYNC, - }, - ]; - - if (CoreApp.isIOS()) { - sections.push({ - name: 'core.sharedfiles.sharedfiles', - path: SHAREDFILES_PAGE_NAME + '/list/root', - icon: 'fas-folder', - params: { manage: true }, - }); - } - - sections.push({ - name: 'core.settings.about', - path: 'about', - icon: 'fas-id-card', - }); - - return sections; - } - } - -/** - * Helper class to manage sections. - */ -class CoreSettingsSectionsManager extends CorePageItemsListManager { - - /** - * @inheritdoc - */ - protected getItemPath(section: CoreSettingsSection): string { - return section.path; - } - - /** - * @inheritdoc - */ - protected getItemQueryParams(section: CoreSettingsSection): Params { - return section.params || {}; - } - -} - -/** - * Settings section. - */ -export type CoreSettingsSection = { - name: string; - path: string; - icon: string; - params?: Params; -}; diff --git a/src/core/features/settings/pages/site/site.ts b/src/core/features/settings/pages/site/site.ts index 77a366b73..4fc275b2e 100644 --- a/src/core/features/settings/pages/site/site.ts +++ b/src/core/features/settings/pages/site/site.ts @@ -13,10 +13,9 @@ // limitations under the License. import { AfterViewInit, Component, OnDestroy, ViewChild } from '@angular/core'; -import { Params } from '@angular/router'; import { IonRefresher } from '@ionic/angular'; -import { CoreSettingsDelegate, CoreSettingsHandlerToDisplay } from '../../services/settings-delegate'; +import { CoreSettingsHandlerToDisplay } from '../../services/settings-delegate'; import { CoreEventObserver, CoreEvents } from '@singletons/events'; import { CoreSites } from '@services/sites'; import { CoreDomUtils } from '@services/utils/dom'; @@ -24,8 +23,10 @@ import { CoreSettingsHelper, CoreSiteSpaceUsage } from '../../services/settings- import { CoreApp } from '@services/app'; import { Translate } from '@singletons'; import { CoreNavigator } from '@services/navigator'; -import { CorePageItemsListManager } from '@classes/page-items-list-manager'; import { CoreSplitViewComponent } from '@components/split-view/split-view'; +import { CoreListItemsManager } from '@classes/items-management/list-items-manager'; +import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker'; +import { CoreSettingsHandlersSource } from '@features/settings/classes/settings-handlers-source'; /** * Page that displays the list of site settings pages. @@ -38,7 +39,7 @@ export class CoreSitePreferencesPage implements AfterViewInit, OnDestroy { @ViewChild(CoreSplitViewComponent) splitView!: CoreSplitViewComponent; - handlers: CoreSettingsSitePreferencesManager; + handlers: CoreListItemsManager; isIOS: boolean; siteId: string; @@ -51,10 +52,12 @@ export class CoreSitePreferencesPage implements AfterViewInit, OnDestroy { protected isDestroyed = false; constructor() { - this.isIOS = CoreApp.isIOS(); this.siteId = CoreSites.getCurrentSiteId(); - this.handlers = new CoreSettingsSitePreferencesManager(CoreSitePreferencesPage); + + const source = CoreRoutedItemsManagerSourcesTracker.getOrCreateSource(CoreSettingsHandlersSource, []); + + this.handlers = new CoreListItemsManager(source, CoreSitePreferencesPage); this.sitesObserver = CoreEvents.on(CoreEvents.SITE_UPDATED, () => { this.refreshData(); @@ -70,14 +73,14 @@ export class CoreSitePreferencesPage implements AfterViewInit, OnDestroy { try { await this.fetchData(); } finally { - const handler = pageToOpen ? this.handlers.items.find(handler => handler.page == pageToOpen) : undefined; if (handler) { - this.handlers.select(handler); this.handlers.watchSplitViewOutlet(this.splitView); + + await this.handlers.select(handler); } else { - this.handlers.start(this.splitView); + await this.handlers.start(this.splitView); } } } @@ -86,7 +89,7 @@ export class CoreSitePreferencesPage implements AfterViewInit, OnDestroy { * Fetch Data. */ protected async fetchData(): Promise { - this.handlers.setItems(CoreSettingsDelegate.getHandlers()); + await this.handlers.load(); this.spaceUsage = await CoreSettingsHelper.getSiteSpaceUsage(this.siteId); } @@ -122,6 +125,7 @@ export class CoreSitePreferencesPage implements AfterViewInit, OnDestroy { * @param refresher Refresher. */ refreshData(refresher?: IonRefresher): void { + this.handlers.getSource().setDirty(true); this.fetchData().finally(() => { refresher?.complete(); }); @@ -171,24 +175,3 @@ export class CoreSitePreferencesPage implements AfterViewInit, OnDestroy { } } - -/** - * Helper class to manage sections. - */ -class CoreSettingsSitePreferencesManager extends CorePageItemsListManager { - - /** - * @inheritdoc - */ - protected getItemPath(handler: CoreSettingsHandlerToDisplay): string { - return handler.page; - } - - /** - * @inheritdoc - */ - protected getItemQueryParams(handler: CoreSettingsHandlerToDisplay): Params { - return handler.params || {}; - } - -}