diff --git a/src/core/features/courses/courses-lazy.module.ts b/src/core/features/courses/courses-lazy.module.ts index 0fa55f3fb..ad6fc7cdf 100644 --- a/src/core/features/courses/courses-lazy.module.ts +++ b/src/core/features/courses/courses-lazy.module.ts @@ -33,16 +33,10 @@ const routes: Routes = [ .then(m => m.CoreCoursesCategoriesPageModule), }, { - path: 'all', + path: 'list', loadChildren: () => - import('./pages/available-courses/available-courses.module') - .then(m => m.CoreCoursesAvailableCoursesPageModule), - }, - { - path: 'search', - loadChildren: () => - import('./pages/search/search.module') - .then(m => m.CoreCoursesSearchPageModule), + import('./pages/list/list.module') + .then(m => m.CoreCoursesListPageModule), }, { path: 'my', diff --git a/src/core/features/courses/pages/available-courses/available-courses.html b/src/core/features/courses/pages/available-courses/available-courses.html deleted file mode 100644 index 07aebce00..000000000 --- a/src/core/features/courses/pages/available-courses/available-courses.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - - -

{{ 'core.courses.availablecourses' | translate }}

-
-
- - - - - - - - - - - diff --git a/src/core/features/courses/pages/available-courses/available-courses.module.ts b/src/core/features/courses/pages/available-courses/available-courses.module.ts deleted file mode 100644 index c272cfa72..000000000 --- a/src/core/features/courses/pages/available-courses/available-courses.module.ts +++ /dev/null @@ -1,41 +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 { NgModule } from '@angular/core'; -import { RouterModule, Routes } from '@angular/router'; - -import { CoreSharedModule } from '@/core/shared.module'; -import { CoreCoursesComponentsModule } from '../../components/components.module'; - -import { CoreCoursesAvailableCoursesPage } from './available-courses'; - -const routes: Routes = [ - { - path: '', - component: CoreCoursesAvailableCoursesPage, - }, -]; - -@NgModule({ - imports: [ - RouterModule.forChild(routes), - CoreSharedModule, - CoreCoursesComponentsModule, - ], - declarations: [ - CoreCoursesAvailableCoursesPage, - ], - exports: [RouterModule], -}) -export class CoreCoursesAvailableCoursesPageModule { } diff --git a/src/core/features/courses/pages/available-courses/available-courses.ts b/src/core/features/courses/pages/available-courses/available-courses.ts deleted file mode 100644 index c7a5b70ef..000000000 --- a/src/core/features/courses/pages/available-courses/available-courses.ts +++ /dev/null @@ -1,78 +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 { CoreSites } from '@services/sites'; -import { CoreDomUtils } from '@services/utils/dom'; -import { CoreCourses, CoreCourseSearchedData } from '../../services/courses'; - -/** - * Page that displays available courses in current site. - */ -@Component({ - selector: 'page-core-courses-available-courses', - templateUrl: 'available-courses.html', -}) -export class CoreCoursesAvailableCoursesPage implements OnInit { - - courses: CoreCourseSearchedData[] = []; - coursesLoaded = false; - - /** - * View loaded. - */ - ngOnInit(): void { - this.loadCourses().finally(() => { - this.coursesLoaded = true; - }); - } - - /** - * Load the courses. - * - * @return Promise resolved when done. - */ - protected async loadCourses(): Promise { - const frontpageCourseId = CoreSites.getCurrentSiteHomeId(); - - try { - const courses = await CoreCourses.getCoursesByField(); - - this.courses = courses.filter((course) => course.id != frontpageCourseId); - } catch (error) { - CoreDomUtils.showErrorModalDefault(error, 'core.courses.errorloadcourses', true); - } - } - - /** - * Refresh the courses. - * - * @param refresher Refresher. - */ - refreshCourses(refresher: IonRefresher): void { - const promises: Promise[] = []; - - promises.push(CoreCourses.invalidateUserCourses()); - promises.push(CoreCourses.invalidateCoursesByField()); - - Promise.all(promises).finally(() => { - this.loadCourses().finally(() => { - refresher?.complete(); - }); - }); - } - -} diff --git a/src/core/features/courses/pages/dashboard/dashboard.ts b/src/core/features/courses/pages/dashboard/dashboard.ts index 1c67c49c9..56ef7d56c 100644 --- a/src/core/features/courses/pages/dashboard/dashboard.ts +++ b/src/core/features/courses/pages/dashboard/dashboard.ts @@ -167,7 +167,7 @@ export class CoreCoursesDashboardPage implements OnInit, OnDestroy { * Go to search courses. */ async openSearch(): Promise { - CoreNavigator.navigateToSitePath('/courses/search'); + CoreNavigator.navigateToSitePath('/courses/list', { params : { mode: 'search' } }); } /** diff --git a/src/core/features/courses/pages/list/list.html b/src/core/features/courses/pages/list/list.html new file mode 100644 index 000000000..3b2471ad6 --- /dev/null +++ b/src/core/features/courses/pages/list/list.html @@ -0,0 +1,36 @@ + + + + + +

{{ 'core.courses.availablecourses' | translate }}

+
+
+ + + + + + + + + + +

{{ 'core.courses.totalcoursesearchresults' | translate:{$a: searchTotal} }}

+
+
+ + + + + + + + + + + +
+
diff --git a/src/core/features/courses/pages/search/search.module.ts b/src/core/features/courses/pages/list/list.module.ts similarity index 87% rename from src/core/features/courses/pages/search/search.module.ts rename to src/core/features/courses/pages/list/list.module.ts index 497e6f4d1..c0d3a2c36 100644 --- a/src/core/features/courses/pages/search/search.module.ts +++ b/src/core/features/courses/pages/list/list.module.ts @@ -19,12 +19,12 @@ import { CoreSharedModule } from '@/core/shared.module'; import { CoreCoursesComponentsModule } from '../../components/components.module'; import { CoreSearchComponentsModule } from '@features/search/components/components.module'; -import { CoreCoursesSearchPage } from './search'; +import { CoreCoursesListPage } from './list'; const routes: Routes = [ { path: '', - component: CoreCoursesSearchPage, + component: CoreCoursesListPage, }, ]; @@ -36,8 +36,8 @@ const routes: Routes = [ CoreSearchComponentsModule, ], declarations: [ - CoreCoursesSearchPage, + CoreCoursesListPage, ], exports: [RouterModule], }) -export class CoreCoursesSearchPageModule { } +export class CoreCoursesListPageModule { } diff --git a/src/core/features/courses/pages/list/list.ts b/src/core/features/courses/pages/list/list.ts new file mode 100644 index 000000000..12c0cf317 --- /dev/null +++ b/src/core/features/courses/pages/list/list.ts @@ -0,0 +1,211 @@ +// (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, OnDestroy, OnInit } from '@angular/core'; +import { IonRefresher } from '@ionic/angular'; +import { CoreNavigator } from '@services/navigator'; +import { CoreSites } from '@services/sites'; +import { CoreDomUtils } from '@services/utils/dom'; +import { CoreEventObserver, CoreEvents } from '@singletons/events'; +import { CoreCourseBasicSearchedData, CoreCourses } from '../../services/courses'; + +type CoreCoursesListMode = 'search' | 'all'; + +/** + * Page that shows a list of courses. + */ +@Component({ + selector: 'page-core-courses-list', + templateUrl: 'list.html', +}) +export class CoreCoursesListPage implements OnInit, OnDestroy { + + searchEnabled = false; + searchMode = false; + searchCanLoadMore = false; + searchLoadMoreError = false; + searchTotal = 0; + + mode: CoreCoursesListMode = 'all'; + + courses: CoreCourseBasicSearchedData[] = []; + coursesLoaded = false; + + protected currentSiteId: string; + protected frontpageCourseId: number; + protected searchPage = 0; + protected searchText = ''; + protected siteUpdatedObserver: CoreEventObserver; + + constructor() { + this.currentSiteId = CoreSites.getRequiredCurrentSite().getId(); + this.frontpageCourseId = CoreSites.getRequiredCurrentSite().getSiteHomeId(); + + // Refresh the enabled flags if site is updated. + this.siteUpdatedObserver = CoreEvents.on(CoreEvents.SITE_UPDATED, () => { + this.searchEnabled = !CoreCourses.isSearchCoursesDisabledInSite(); + + if (!this.searchEnabled) { + this.searchMode = false; + + this.fetchCourses(); + } + }, this.currentSiteId); + } + + /** + * @inheritdoc + */ + ngOnInit(): void { + this.mode = CoreNavigator.getRouteParam('mode') || this.mode; + + if (this.mode == 'search') { + this.searchMode = true; + } + + this.searchEnabled = !CoreCourses.isSearchCoursesDisabledInSite(); + if (!this.searchEnabled) { + this.searchMode = false; + } + + this.fetchCourses(); + } + + /** + * Load the course list. + * + * @return Promise resolved when done. + */ + protected async fetchCourses(): Promise { + try { + if (this.searchMode && this.searchText) { + await this.search(this.searchText); + } else { + await this.loadAvailableCourses(); + } + } finally { + this.coursesLoaded = true; + } + } + + /** + * Load the courses. + * + * @return Promise resolved when done. + */ + protected async loadAvailableCourses(): Promise { + try { + const courses = await CoreCourses.getCoursesByField(); + + this.courses = courses.filter((course) => course.id != this.frontpageCourseId); + } catch (error) { + CoreDomUtils.showErrorModalDefault(error, 'core.courses.errorloadcourses', true); + } + } + + /** + * Refresh the courses. + * + * @param refresher Refresher. + */ + refreshCourses(refresher: IonRefresher): void { + const promises: Promise[] = []; + + promises.push(CoreCourses.invalidateUserCourses()); + promises.push(CoreCourses.invalidateCoursesByField()); + + Promise.all(promises).finally(() => { + this.fetchCourses().finally(() => { + refresher?.complete(); + }); + }); + } + + /** + * Search a new text. + * + * @param text The text to search. + */ + async search(text: string): Promise { + this.searchMode = true; + this.searchText = text; + this.courses = []; + this.searchPage = 0; + this.searchTotal = 0; + + const modal = await CoreDomUtils.showModalLoading('core.searching', true); + this.searchCourses().finally(() => { + modal.dismiss(); + }); + } + + /** + * Clear search box. + */ + clearSearch(): void { + this.searchText = ''; + this.courses = []; + this.searchPage = 0; + this.searchTotal = 0; + this.searchMode = false; + + this.coursesLoaded = false; + this.fetchCourses(); + } + + /** + * Load more results. + * + * @param infiniteComplete Infinite scroll complete function. Only used from core-infinite-loading. + */ + loadMoreResults(infiniteComplete?: () => void ): void { + this.searchCourses().finally(() => { + infiniteComplete && infiniteComplete(); + }); + } + + /** + * Search courses or load the next page of current search. + * + * @return Promise resolved when done. + */ + protected async searchCourses(): Promise { + this.searchLoadMoreError = false; + + try { + const response = await CoreCourses.search(this.searchText, this.searchPage); + + if (this.searchPage === 0) { + this.courses = response.courses; + } else { + this.courses = this.courses.concat(response.courses); + } + this.searchTotal = response.total; + + this.searchPage++; + this.searchCanLoadMore = this.courses.length < this.searchTotal; + } catch (error) { + this.searchLoadMoreError = true; // Set to prevent infinite calls with infinite-loading. + CoreDomUtils.showErrorModalDefault(error, 'core.courses.errorsearching', true); + } + } + + /** + * @inheritdoc + */ + ngOnDestroy(): void { + this.siteUpdatedObserver?.off(); + } + +} diff --git a/src/core/features/courses/pages/my-courses/my-courses.ts b/src/core/features/courses/pages/my-courses/my-courses.ts index 576d7e8df..675933daf 100644 --- a/src/core/features/courses/pages/my-courses/my-courses.ts +++ b/src/core/features/courses/pages/my-courses/my-courses.ts @@ -204,7 +204,7 @@ export class CoreCoursesMyCoursesPage implements OnInit, OnDestroy { * Go to search courses. */ openSearch(): void { - CoreNavigator.navigateToSitePath('courses/search'); + CoreNavigator.navigateToSitePath('courses/list', { params : { mode: 'search' } }); } /** diff --git a/src/core/features/courses/pages/search/search.html b/src/core/features/courses/pages/search/search.html deleted file mode 100644 index fb33940a9..000000000 --- a/src/core/features/courses/pages/search/search.html +++ /dev/null @@ -1,23 +0,0 @@ - - - - - -

{{ 'core.courses.searchcourses' | translate }}

-
-
- - - - - -

{{ 'core.courses.totalcoursesearchresults' | translate:{$a: total} }}

-
- - - -
- -
diff --git a/src/core/features/courses/pages/search/search.ts b/src/core/features/courses/pages/search/search.ts deleted file mode 100644 index 651aee76f..000000000 --- a/src/core/features/courses/pages/search/search.ts +++ /dev/null @@ -1,100 +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 } from '@angular/core'; -import { CoreDomUtils } from '@services/utils/dom'; -import { CoreCourseBasicSearchedData, CoreCourses } from '../../services/courses'; - -/** - * Page that allows searching for courses. - */ -@Component({ - selector: 'page-core-courses-search', - templateUrl: 'search.html', -}) -export class CoreCoursesSearchPage { - - total = 0; - courses: CoreCourseBasicSearchedData[] = []; - canLoadMore = false; - loadMoreError = false; - - protected page = 0; - protected currentSearch = ''; - - /** - * Search a new text. - * - * @param text The text to search. - */ - async search(text: string): Promise { - this.currentSearch = text; - this.courses = []; - this.page = 0; - this.total = 0; - - const modal = await CoreDomUtils.showModalLoading('core.searching', true); - this.searchCourses().finally(() => { - modal.dismiss(); - }); - } - - /** - * Clear search box. - */ - clearSearch(): void { - this.currentSearch = ''; - this.courses = []; - this.page = 0; - this.total = 0; - } - - /** - * Load more results. - * - * @param infiniteComplete Infinite scroll complete function. Only used from core-infinite-loading. - */ - loadMoreResults(infiniteComplete?: () => void ): void { - this.searchCourses().finally(() => { - infiniteComplete && infiniteComplete(); - }); - } - - /** - * Search courses or load the next page of current search. - * - * @return Promise resolved when done. - */ - protected async searchCourses(): Promise { - this.loadMoreError = false; - - try { - const response = await CoreCourses.search(this.currentSearch, this.page); - - if (this.page === 0) { - this.courses = response.courses; - } else { - this.courses = this.courses.concat(response.courses); - } - this.total = response.total; - - this.page++; - this.canLoadMore = this.courses.length < this.total; - } catch (error) { - this.loadMoreError = true; // Set to prevent infinite calls with infinite-loading. - CoreDomUtils.showErrorModalDefault(error, 'core.courses.errorsearching', true); - } - } - -} diff --git a/src/core/features/courses/services/handlers/courses-index-link.ts b/src/core/features/courses/services/handlers/courses-index-link.ts index 500287239..2ff9c3f92 100644 --- a/src/core/features/courses/services/handlers/courses-index-link.ts +++ b/src/core/features/courses/services/handlers/courses-index-link.ts @@ -42,14 +42,17 @@ export class CoreCoursesIndexLinkHandlerService extends CoreContentLinksHandlerB return [{ action: (siteId): void => { let pageName = CoreCoursesMyCoursesHomeHandlerService.PAGE_NAME; + const pageParams: Params = {}; if (params.categoryid) { pageName += '/categories/' + params.categoryid; } else { - pageName += '/all'; + pageName += '/list'; + pageParams.mode = 'all'; } - CoreNavigator.navigateToSitePath(pageName, { siteId }); + + CoreNavigator.navigateToSitePath(pageName, { params: pageParams, siteId }); }, }]; } diff --git a/src/core/features/sitehome/pages/index/index.ts b/src/core/features/sitehome/pages/index/index.ts index 7f59dae6d..fd1796d53 100644 --- a/src/core/features/sitehome/pages/index/index.ts +++ b/src/core/features/sitehome/pages/index/index.ts @@ -218,14 +218,14 @@ export class CoreSiteHomeIndexPage implements OnInit, OnDestroy { * Go to search courses. */ openSearch(): void { - CoreNavigator.navigateToSitePath('courses/search'); + CoreNavigator.navigateToSitePath('courses/list', { params : { mode: 'search' } }); } /** * Go to available courses. */ openAvailableCourses(): void { - CoreNavigator.navigateToSitePath('courses/all'); + CoreNavigator.navigateToSitePath('courses/list', { params : { mode: 'all' } }); } /**