forked from EVOgeek/Vmeda.Online
		
	MOBILE-3594 sitehome: Course listing components of sitehome
This commit is contained in:
		
							parent
							
								
									97d1c93399
								
							
						
					
					
						commit
						0d66e66fdd
					
				
							
								
								
									
										44
									
								
								src/core/features/courses/components/components.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								src/core/features/courses/components/components.module.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,44 @@ | |||||||
|  | // (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 { CommonModule } from '@angular/common'; | ||||||
|  | import { IonicModule } from '@ionic/angular'; | ||||||
|  | import { TranslateModule } from '@ngx-translate/core'; | ||||||
|  | 
 | ||||||
|  | import { CoreComponentsModule } from '@components/components.module'; | ||||||
|  | import { CoreDirectivesModule } from '@directives/directives.module'; | ||||||
|  | import { CorePipesModule } from '@pipes/pipes.module'; | ||||||
|  | 
 | ||||||
|  | import { CoreCoursesCourseListItemComponent } from './course-list-item/course-list-item'; | ||||||
|  | 
 | ||||||
|  | @NgModule({ | ||||||
|  |     declarations: [ | ||||||
|  |         CoreCoursesCourseListItemComponent, | ||||||
|  |     ], | ||||||
|  |     imports: [ | ||||||
|  |         CommonModule, | ||||||
|  |         IonicModule, | ||||||
|  |         TranslateModule.forChild(), | ||||||
|  |         CoreComponentsModule, | ||||||
|  |         CoreDirectivesModule, | ||||||
|  |         CorePipesModule, | ||||||
|  |     ], | ||||||
|  |     providers: [ | ||||||
|  |     ], | ||||||
|  |     exports: [ | ||||||
|  |         CoreCoursesCourseListItemComponent, | ||||||
|  |     ], | ||||||
|  | }) | ||||||
|  | export class CoreCoursesComponentsModule {} | ||||||
| @ -0,0 +1,14 @@ | |||||||
|  | <ion-item class="ion-text-wrap" (click)="openCourse()" [class.item-disabled]="course.visible == 0" | ||||||
|  | [title]="course.displayname || course.fullname" detail> | ||||||
|  |     <ion-icon name="fas-graduation-cap" slot="start"></ion-icon> | ||||||
|  |     <ion-label> | ||||||
|  |         <h2> | ||||||
|  |             <core-format-text [text]="course.displayname || course.fullname" contextLevel="course" [contextInstanceId]="course.id"> | ||||||
|  |             </core-format-text> | ||||||
|  |         </h2> | ||||||
|  |     </ion-label> | ||||||
|  |     <ng-container *ngIf="!isEnrolled"> | ||||||
|  |         <ion-icon *ngFor="let icon of icons" color="dark" size="small" | ||||||
|  |         [name]="icon.icon" [attr.aria-label]="icon.label | translate" slot="end"></ion-icon> | ||||||
|  |     </ng-container> | ||||||
|  | </ion-item> | ||||||
| @ -0,0 +1,105 @@ | |||||||
|  | // (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, Input, OnInit } from '@angular/core'; | ||||||
|  | import { NavController } from '@ionic/angular'; | ||||||
|  | import { CoreCourseHelper } from '@features/course/services/course.helper'; | ||||||
|  | import { CoreCourses, CoreCourseSearchedData } from '@features/courses/services/courses'; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * This directive is meant to display an item for a list of courses. | ||||||
|  |  * | ||||||
|  |  * Example usage: | ||||||
|  |  * | ||||||
|  |  * <core-courses-course-list-item [course]="course"></core-courses-course-list-item> | ||||||
|  |  */ | ||||||
|  | @Component({ | ||||||
|  |     selector: 'core-courses-course-list-item', | ||||||
|  |     templateUrl: 'core-courses-course-list-item.html', | ||||||
|  | }) | ||||||
|  | export class CoreCoursesCourseListItemComponent implements OnInit { | ||||||
|  | 
 | ||||||
|  |     @Input() course!: CoreCourseSearchedData; // The course to render.
 | ||||||
|  | 
 | ||||||
|  |     icons: CoreCoursesEnrolmentIcons[] = []; | ||||||
|  |     isEnrolled = false; | ||||||
|  | 
 | ||||||
|  |     constructor( | ||||||
|  |         protected navCtrl: NavController, | ||||||
|  |     ) { | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Component being initialized. | ||||||
|  |      */ | ||||||
|  |     async ngOnInit(): Promise<void> { | ||||||
|  |         // Check if the user is enrolled in the course.
 | ||||||
|  |         try { | ||||||
|  |             await CoreCourses.instance.getUserCourse(this.course.id); | ||||||
|  | 
 | ||||||
|  |             this.isEnrolled = true; | ||||||
|  |         } catch { | ||||||
|  |             this.isEnrolled = false; | ||||||
|  |             this.icons = []; | ||||||
|  | 
 | ||||||
|  |             this.course.enrollmentmethods.forEach((instance) => { | ||||||
|  |                 if (instance === 'self') { | ||||||
|  |                     this.icons.push({ | ||||||
|  |                         label: 'core.courses.selfenrolment', | ||||||
|  |                         icon: 'fas-key', | ||||||
|  |                     }); | ||||||
|  |                 } else if (instance === 'guest') { | ||||||
|  |                     this.icons.push({ | ||||||
|  |                         label: 'core.courses.allowguests', | ||||||
|  |                         icon: 'fas-unlock', | ||||||
|  |                     }); | ||||||
|  |                 } else if (instance === 'paypal') { | ||||||
|  |                     this.icons.push({ | ||||||
|  |                         label: 'core.courses.paypalaccepted', | ||||||
|  |                         icon: 'fab-paypal', | ||||||
|  |                     }); | ||||||
|  |                 } | ||||||
|  |             }); | ||||||
|  | 
 | ||||||
|  |             if (this.icons.length == 0) { | ||||||
|  |                 this.icons.push({ | ||||||
|  |                     label: 'core.courses.notenrollable', | ||||||
|  |                     icon: 'fas-lock', | ||||||
|  |                 }); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Open a course. | ||||||
|  |      * | ||||||
|  |      * @param course The course to open. | ||||||
|  |      */ | ||||||
|  |     openCourse(): void { | ||||||
|  |         if (this.isEnrolled) { | ||||||
|  |             CoreCourseHelper.instance.openCourse(this.course); | ||||||
|  |         } else { | ||||||
|  |             this.navCtrl.navigateForward('/courses/preview', { queryParams: { course: this.course } }); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Enrolment icons to show on the list with a label. | ||||||
|  |  */ | ||||||
|  | export type CoreCoursesEnrolmentIcons = { | ||||||
|  |     label: string; | ||||||
|  |     icon: string; | ||||||
|  | }; | ||||||
| @ -13,12 +13,12 @@ | |||||||
| // limitations under the License.
 | // limitations under the License.
 | ||||||
| 
 | 
 | ||||||
| import { NgModule } from '@angular/core'; | import { NgModule } from '@angular/core'; | ||||||
| import { Routes } from '@angular/router'; | import { RouterModule, Routes } from '@angular/router'; | ||||||
| import { CoreHomeRoutingModule } from '../mainmenu/pages/home/home-routing.module'; | import { CoreHomeRoutingModule } from '../mainmenu/pages/home/home-routing.module'; | ||||||
| import { CoreHomeDelegate } from '../mainmenu/services/home.delegate'; | import { CoreHomeDelegate } from '../mainmenu/services/home.delegate'; | ||||||
| import { CoreDashboardHomeHandler } from './services/handlers/dashboard.home'; | import { CoreDashboardHomeHandler } from './services/handlers/dashboard.home'; | ||||||
| 
 | 
 | ||||||
| const routes: Routes = [ | const homeRoutes: Routes = [ | ||||||
|     { |     { | ||||||
|         path: 'dashboard', |         path: 'dashboard', | ||||||
|         loadChildren: () => |         loadChildren: () => | ||||||
| @ -26,9 +26,50 @@ const routes: Routes = [ | |||||||
|     }, |     }, | ||||||
| ]; | ]; | ||||||
| 
 | 
 | ||||||
|  | const routes: Routes = [ | ||||||
|  |     { | ||||||
|  |         path: 'courses', | ||||||
|  |         children: [ | ||||||
|  |             { | ||||||
|  |                 path: '', | ||||||
|  |                 redirectTo: 'all', | ||||||
|  |                 pathMatch: 'full', | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |                 path: 'categories', | ||||||
|  |                 redirectTo: 'categories/root', // Fake "id".
 | ||||||
|  |                 pathMatch: 'full', | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |                 path: 'categories/:id', | ||||||
|  |                 loadChildren: () => | ||||||
|  |                     import('@features/courses/pages/categories/categories.page.module').then(m => m.CoreCoursesCategoriesPageModule), | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |                 path: 'all', | ||||||
|  |                 loadChildren: () => | ||||||
|  |                     import('@features/courses/pages/available-courses/available-courses.page.module') | ||||||
|  |                         .then(m => m.CoreCoursesAvailableCoursesPageModule), | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |                 path: 'search', | ||||||
|  |                 loadChildren: () => | ||||||
|  |                     import('@features/courses/pages/search/search.page.module') | ||||||
|  |                         .then(m => m.CoreCoursesSearchPageModule), | ||||||
|  |             }, | ||||||
|  |         ], | ||||||
|  |     }, | ||||||
|  | ]; | ||||||
|  | 
 | ||||||
| @NgModule({ | @NgModule({ | ||||||
|     imports: [CoreHomeRoutingModule.forChild(routes)], |     imports: [ | ||||||
|     exports: [CoreHomeRoutingModule], |         CoreHomeRoutingModule.forChild(homeRoutes), | ||||||
|  |         RouterModule.forChild(routes), | ||||||
|  |     ], | ||||||
|  |     exports: [ | ||||||
|  |         CoreHomeRoutingModule, | ||||||
|  |         RouterModule, | ||||||
|  |     ], | ||||||
|     providers: [ |     providers: [ | ||||||
|         CoreDashboardHomeHandler, |         CoreDashboardHomeHandler, | ||||||
|     ], |     ], | ||||||
|  | |||||||
| @ -0,0 +1,19 @@ | |||||||
|  | <ion-header> | ||||||
|  |     <ion-toolbar> | ||||||
|  |         <ion-buttons slot="start"> | ||||||
|  |             <ion-back-button [attr.aria-label]="'core.back' | translate"></ion-back-button> | ||||||
|  |         </ion-buttons> | ||||||
|  |         <ion-title>{{ 'core.courses.availablecourses' | translate }}</ion-title> | ||||||
|  |     </ion-toolbar> | ||||||
|  | </ion-header> | ||||||
|  | <ion-content> | ||||||
|  |     <ion-refresher slot="fixed" [disabled]="!coursesLoaded" (ionRefresh)="refreshCourses($event)"> | ||||||
|  |         <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content> | ||||||
|  |     </ion-refresher> | ||||||
|  |     <core-loading [hideUntil]="coursesLoaded"> | ||||||
|  |         <ng-container *ngIf="courses.length > 0"> | ||||||
|  |             <core-courses-course-list-item *ngFor="let course of courses" [course]="course"></core-courses-course-list-item> | ||||||
|  |         </ng-container> | ||||||
|  |         <core-empty-box *ngIf="!courses.length" icon="fas-graduation-cap" [message]="'core.courses.nocourses' | translate"></core-empty-box> | ||||||
|  |     </core-loading> | ||||||
|  | </ion-content> | ||||||
| @ -0,0 +1,50 @@ | |||||||
|  | // (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 { CommonModule } from '@angular/common'; | ||||||
|  | import { RouterModule, Routes } from '@angular/router'; | ||||||
|  | import { IonicModule } from '@ionic/angular'; | ||||||
|  | import { TranslateModule } from '@ngx-translate/core'; | ||||||
|  | 
 | ||||||
|  | import { CoreComponentsModule } from '@components/components.module'; | ||||||
|  | import { CoreDirectivesModule } from '@directives/directives.module'; | ||||||
|  | import { CoreCoursesComponentsModule } from '../../components/components.module'; | ||||||
|  | 
 | ||||||
|  | import { CoreCoursesAvailableCoursesPage } from './available-courses.page'; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | const routes: Routes = [ | ||||||
|  |     { | ||||||
|  |         path: '', | ||||||
|  |         component: CoreCoursesAvailableCoursesPage, | ||||||
|  |     }, | ||||||
|  | ]; | ||||||
|  | 
 | ||||||
|  | @NgModule({ | ||||||
|  |     imports: [ | ||||||
|  |         RouterModule.forChild(routes), | ||||||
|  |         CommonModule, | ||||||
|  |         IonicModule, | ||||||
|  |         TranslateModule.forChild(), | ||||||
|  |         CoreComponentsModule, | ||||||
|  |         CoreDirectivesModule, | ||||||
|  |         CoreCoursesComponentsModule, | ||||||
|  |     ], | ||||||
|  |     declarations: [ | ||||||
|  |         CoreCoursesAvailableCoursesPage, | ||||||
|  |     ], | ||||||
|  |     exports: [RouterModule], | ||||||
|  | }) | ||||||
|  | export class CoreCoursesAvailableCoursesPageModule { } | ||||||
| @ -0,0 +1,78 @@ | |||||||
|  | // (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<void> { | ||||||
|  |         const frontpageCourseId = CoreSites.instance.getCurrentSite()!.getSiteHomeId(); | ||||||
|  | 
 | ||||||
|  |         try { | ||||||
|  |             const courses = await CoreCourses.instance.getCoursesByField(); | ||||||
|  | 
 | ||||||
|  |             this.courses = courses.filter((course) => course.id != frontpageCourseId); | ||||||
|  |         } catch (error) { | ||||||
|  |             CoreDomUtils.instance.showErrorModalDefault(error, 'core.courses.errorloadcourses', true); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Refresh the courses. | ||||||
|  |      * | ||||||
|  |      * @param refresher Refresher. | ||||||
|  |      */ | ||||||
|  |     refreshCourses(refresher: CustomEvent<IonRefresher>): void { | ||||||
|  |         const promises: Promise<void>[] = []; | ||||||
|  | 
 | ||||||
|  |         promises.push(CoreCourses.instance.invalidateUserCourses()); | ||||||
|  |         promises.push(CoreCourses.instance.invalidateCoursesByField()); | ||||||
|  | 
 | ||||||
|  |         Promise.all(promises).finally(() => { | ||||||
|  |             this.loadCourses().finally(() => { | ||||||
|  |                 refresher?.detail.complete(); | ||||||
|  |             }); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
							
								
								
									
										67
									
								
								src/core/features/courses/pages/categories/categories.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								src/core/features/courses/pages/categories/categories.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,67 @@ | |||||||
|  | <ion-header> | ||||||
|  |     <ion-toolbar> | ||||||
|  |         <ion-buttons slot="start"> | ||||||
|  |             <ion-back-button [attr.aria-label]="'core.back' | translate"></ion-back-button> | ||||||
|  |         </ion-buttons> | ||||||
|  |         <ion-title> | ||||||
|  |             <core-format-text [text]="title" contextLevel="coursecat" [contextInstanceId]="currentCategory && currentCategory!.id"> | ||||||
|  |             </core-format-text> | ||||||
|  |         </ion-title> | ||||||
|  |     </ion-toolbar> | ||||||
|  | </ion-header> | ||||||
|  | <ion-content> | ||||||
|  |     <ion-refresher slot="fixed" [disabled]="!categoriesLoaded" (ionRefresh)="refreshCategories($event)"> | ||||||
|  |         <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content> | ||||||
|  |     </ion-refresher> | ||||||
|  |     <core-loading [hideUntil]="categoriesLoaded"> | ||||||
|  |         <ion-item *ngIf="currentCategory" class="ion-text-wrap"> | ||||||
|  |             <ion-icon name="fas-folder" slot="start"></ion-icon> | ||||||
|  |             <ion-label> | ||||||
|  |                 <h2> | ||||||
|  |                     <core-format-text [text]="currentCategory!.name" contextLevel="coursecat" | ||||||
|  |                     [contextInstanceId]="currentCategory!.id"></core-format-text> | ||||||
|  |                 </h2> | ||||||
|  |             </ion-label> | ||||||
|  |         </ion-item> | ||||||
|  |         <ion-item class="ion-text-wrap" *ngIf="currentCategory && currentCategory!.description"> | ||||||
|  |             <ion-label> | ||||||
|  |                 <h2> | ||||||
|  |                     <core-format-text [text]="currentCategory!.description" maxHeight="60" contextLevel="coursecat" | ||||||
|  |             [contextInstanceId]="currentCategory!.id"></core-format-text> | ||||||
|  |                 </h2> | ||||||
|  |             </ion-label> | ||||||
|  |         </ion-item> | ||||||
|  | 
 | ||||||
|  |         <div *ngIf="categories.length > 0"> | ||||||
|  |             <ion-item-divider> | ||||||
|  |                 <ion-label> | ||||||
|  |                     <h2>{{ 'core.courses.categories' | translate }}</h2> | ||||||
|  |                 </ion-label> | ||||||
|  |             </ion-item-divider> | ||||||
|  |             <section *ngFor="let category of categories"> | ||||||
|  |                 <ion-item class="ion-text-wrap" router-direction="forward" [routerLink]="['/courses/categories', category.id]" | ||||||
|  |                 [title]="category.name" detail> | ||||||
|  |                     <ion-icon name="fas-folder" slot="start"></ion-icon> | ||||||
|  |                     <ion-label> | ||||||
|  |                         <h2> | ||||||
|  |                             <core-format-text [text]="category.name" contextLevel="coursecat" [contextInstanceId]="category.id"> | ||||||
|  |                             </core-format-text> | ||||||
|  |                         </h2> | ||||||
|  |                     </ion-label> | ||||||
|  |                     <ion-badge slot="end" *ngIf="category.coursecount > 0" color="light">{{category.coursecount}}</ion-badge> | ||||||
|  |                 </ion-item> | ||||||
|  |             </section> | ||||||
|  |         </div> | ||||||
|  | 
 | ||||||
|  |         <div *ngIf="courses.length > 0"> | ||||||
|  |             <ion-item-divider> | ||||||
|  |                 <ion-label> | ||||||
|  |                     <h2>{{ 'core.courses.courses' | translate }}</h2> | ||||||
|  |                 </ion-label> | ||||||
|  |             </ion-item-divider> | ||||||
|  |             <core-courses-course-list-item *ngFor="let course of courses" [course]="course"></core-courses-course-list-item> | ||||||
|  |         </div> | ||||||
|  |         <core-empty-box *ngIf="!categories.length && !courses.length" icon="ionic" [message]="'core.courses.nocoursesyet' | translate"> | ||||||
|  |         </core-empty-box> | ||||||
|  |     </core-loading> | ||||||
|  | </ion-content> | ||||||
| @ -0,0 +1,50 @@ | |||||||
|  | // (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 { CommonModule } from '@angular/common'; | ||||||
|  | import { RouterModule, Routes } from '@angular/router'; | ||||||
|  | import { IonicModule } from '@ionic/angular'; | ||||||
|  | import { TranslateModule } from '@ngx-translate/core'; | ||||||
|  | 
 | ||||||
|  | import { CoreComponentsModule } from '@components/components.module'; | ||||||
|  | import { CoreDirectivesModule } from '@directives/directives.module'; | ||||||
|  | import { CoreCoursesComponentsModule } from '../../components/components.module'; | ||||||
|  | 
 | ||||||
|  | import { CoreCoursesCategoriesPage } from './categories.page'; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | const routes: Routes = [ | ||||||
|  |     { | ||||||
|  |         path: '', | ||||||
|  |         component: CoreCoursesCategoriesPage, | ||||||
|  |     }, | ||||||
|  | ]; | ||||||
|  | 
 | ||||||
|  | @NgModule({ | ||||||
|  |     imports: [ | ||||||
|  |         RouterModule.forChild(routes), | ||||||
|  |         CommonModule, | ||||||
|  |         IonicModule, | ||||||
|  |         TranslateModule.forChild(), | ||||||
|  |         CoreComponentsModule, | ||||||
|  |         CoreDirectivesModule, | ||||||
|  |         CoreCoursesComponentsModule, | ||||||
|  |     ], | ||||||
|  |     declarations: [ | ||||||
|  |         CoreCoursesCategoriesPage, | ||||||
|  |     ], | ||||||
|  |     exports: [RouterModule], | ||||||
|  | }) | ||||||
|  | export class CoreCoursesCategoriesPageModule { } | ||||||
							
								
								
									
										123
									
								
								src/core/features/courses/pages/categories/categories.page.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								src/core/features/courses/pages/categories/categories.page.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,123 @@ | |||||||
|  | // (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, NavController } from '@ionic/angular'; | ||||||
|  | import { CoreSites } from '@services/sites'; | ||||||
|  | import { CoreDomUtils } from '@services/utils/dom'; | ||||||
|  | import { CoreUtils } from '@services/utils/utils'; | ||||||
|  | import { CoreCategoryData, CoreCourses, CoreCourseSearchedData } from '../../services/courses'; | ||||||
|  | import { Translate } from '@singletons/core.singletons'; | ||||||
|  | import { ActivatedRoute } from '@angular/router'; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Page that displays a list of categories and the courses in the current category if any. | ||||||
|  |  */ | ||||||
|  | @Component({ | ||||||
|  |     selector: 'page-core-courses-categories', | ||||||
|  |     templateUrl: 'categories.html', | ||||||
|  | }) | ||||||
|  | export class CoreCoursesCategoriesPage implements OnInit { | ||||||
|  | 
 | ||||||
|  |     title: string; | ||||||
|  |     currentCategory?: CoreCategoryData; | ||||||
|  |     categories: CoreCategoryData[] = []; | ||||||
|  |     courses: CoreCourseSearchedData[] = []; | ||||||
|  |     categoriesLoaded = false; | ||||||
|  | 
 | ||||||
|  |     protected categoryId = 0; | ||||||
|  | 
 | ||||||
|  |     constructor( | ||||||
|  |         protected navCtrl: NavController, | ||||||
|  |         protected route: ActivatedRoute, | ||||||
|  |     ) { | ||||||
|  |         this.title = Translate.instance.instant('core.courses.categories'); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * View loaded. | ||||||
|  |      */ | ||||||
|  |     ngOnInit(): void { | ||||||
|  |         this.categoryId = parseInt(this.route.snapshot.params['id'], 0) || 0; | ||||||
|  | 
 | ||||||
|  |         this.fetchCategories().finally(() => { | ||||||
|  |             this.categoriesLoaded = true; | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Fetch the categories. | ||||||
|  |      * | ||||||
|  |      * @return Promise resolved when done. | ||||||
|  |      */ | ||||||
|  |     protected async fetchCategories(): Promise<void> { | ||||||
|  |         try{ | ||||||
|  |             const categories: CoreCategoryData[] = await CoreCourses.instance.getCategories(this.categoryId, true); | ||||||
|  | 
 | ||||||
|  |             this.currentCategory = undefined; | ||||||
|  | 
 | ||||||
|  |             const index = categories.findIndex((category) => category.id == this.categoryId); | ||||||
|  | 
 | ||||||
|  |             if (index >= 0) { | ||||||
|  |                 this.currentCategory = categories[index]; | ||||||
|  |                 // Delete current Category to avoid problems with the formatTree.
 | ||||||
|  |                 delete categories[index]; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             // Sort by depth and sortorder to avoid problems formatting Tree.
 | ||||||
|  |             categories.sort((a, b) => { | ||||||
|  |                 if (a.depth == b.depth) { | ||||||
|  |                     return (a.sortorder > b.sortorder) ? 1 : ((b.sortorder > a.sortorder) ? -1 : 0); | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 return a.depth > b.depth ? 1 : -1; | ||||||
|  |             }); | ||||||
|  | 
 | ||||||
|  |             this.categories = CoreUtils.instance.formatTree(categories, 'parent', 'id', this.categoryId); | ||||||
|  | 
 | ||||||
|  |             if (this.currentCategory) { | ||||||
|  |                 this.title = this.currentCategory.name; | ||||||
|  | 
 | ||||||
|  |                 try { | ||||||
|  |                     this.courses = await CoreCourses.instance.getCoursesByField('category', this.categoryId); | ||||||
|  |                 } catch (error) { | ||||||
|  |                     CoreDomUtils.instance.showErrorModalDefault(error, 'core.courses.errorloadcourses', true); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } catch (error) { | ||||||
|  |             CoreDomUtils.instance.showErrorModalDefault(error, 'core.courses.errorloadcategories', true); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Refresh the categories. | ||||||
|  |      * | ||||||
|  |      * @param refresher Refresher. | ||||||
|  |      */ | ||||||
|  |     refreshCategories(refresher?: CustomEvent<IonRefresher>): void { | ||||||
|  |         const promises: Promise<void>[] = []; | ||||||
|  | 
 | ||||||
|  |         promises.push(CoreCourses.instance.invalidateUserCourses()); | ||||||
|  |         promises.push(CoreCourses.instance.invalidateCategories(this.categoryId, true)); | ||||||
|  |         promises.push(CoreCourses.instance.invalidateCoursesByField('category', this.categoryId)); | ||||||
|  |         promises.push(CoreSites.instance.getCurrentSite()!.invalidateConfig()); | ||||||
|  | 
 | ||||||
|  |         Promise.all(promises).finally(() => { | ||||||
|  |             this.fetchCategories().finally(() => { | ||||||
|  |                 refresher?.detail.complete(); | ||||||
|  |             }); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
							
								
								
									
										24
									
								
								src/core/features/courses/pages/search/search.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								src/core/features/courses/pages/search/search.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,24 @@ | |||||||
|  | <ion-header> | ||||||
|  |     <ion-toolbar> | ||||||
|  |         <ion-buttons slot="start"> | ||||||
|  |             <ion-back-button [attr.aria-label]="'core.back' | translate"></ion-back-button> | ||||||
|  |         </ion-buttons> | ||||||
|  |         <ion-title>{{ 'core.courses.searchcourses' | translate }}</ion-title> | ||||||
|  |     </ion-toolbar> | ||||||
|  | </ion-header> | ||||||
|  | <ion-content> | ||||||
|  |     <core-search-box (onSubmit)="search($event)" (onClear)="clearSearch($event)" | ||||||
|  |     [placeholder]="'core.courses.search' | translate" [searchLabel]="'core.courses.search' | translate" autoFocus="true" | ||||||
|  |     searchArea="CoreCoursesSearch"></core-search-box> | ||||||
|  | 
 | ||||||
|  |     <ng-container *ngIf="total > 0"> | ||||||
|  |         <ion-item-divider> | ||||||
|  |             <ion-label><h2>{{ 'core.courses.totalcoursesearchresults' | translate:{$a: total} }}</h2></ion-label> | ||||||
|  |         </ion-item-divider> | ||||||
|  |         <core-courses-course-list-item *ngFor="let course of courses" [course]="course"></core-courses-course-list-item> | ||||||
|  |         <core-infinite-loading [enabled]="canLoadMore" (action)="loadMoreResults($event)" [error]="loadMoreError"> | ||||||
|  |         </core-infinite-loading> | ||||||
|  |     </ng-container> | ||||||
|  |     <core-empty-box *ngIf="total == 0" icon="search" [message]="'core.courses.nosearchresults' | translate"></core-empty-box> | ||||||
|  | </ion-content> | ||||||
|  | 
 | ||||||
							
								
								
									
										52
									
								
								src/core/features/courses/pages/search/search.page.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								src/core/features/courses/pages/search/search.page.module.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,52 @@ | |||||||
|  | // (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 { CommonModule } from '@angular/common'; | ||||||
|  | import { RouterModule, Routes } from '@angular/router'; | ||||||
|  | import { IonicModule } from '@ionic/angular'; | ||||||
|  | import { TranslateModule } from '@ngx-translate/core'; | ||||||
|  | 
 | ||||||
|  | import { CoreComponentsModule } from '@components/components.module'; | ||||||
|  | import { CoreDirectivesModule } from '@directives/directives.module'; | ||||||
|  | import { CoreCoursesComponentsModule } from '../../components/components.module'; | ||||||
|  | import { CoreSearchComponentsModule } from '@features/search/components/components.module'; | ||||||
|  | 
 | ||||||
|  | import { CoreCoursesSearchPage } from './search.page'; | ||||||
|  | 
 | ||||||
|  | const routes: Routes = [ | ||||||
|  |     { | ||||||
|  |         path: '', | ||||||
|  |         component: CoreCoursesSearchPage, | ||||||
|  |     }, | ||||||
|  | ]; | ||||||
|  | 
 | ||||||
|  | @NgModule({ | ||||||
|  |     imports: [ | ||||||
|  |         RouterModule.forChild(routes), | ||||||
|  |         CommonModule, | ||||||
|  |         IonicModule, | ||||||
|  |         TranslateModule.forChild(), | ||||||
|  |         CoreComponentsModule, | ||||||
|  |         CoreDirectivesModule, | ||||||
|  |         CoreCoursesComponentsModule, | ||||||
|  |         CoreSearchComponentsModule, | ||||||
|  |     ], | ||||||
|  |     declarations: [ | ||||||
|  |         CoreCoursesSearchPage, | ||||||
|  |     ], | ||||||
|  |     exports: [RouterModule], | ||||||
|  | }) | ||||||
|  | export class CoreCoursesSearchPageModule { } | ||||||
|  | 
 | ||||||
							
								
								
									
										100
									
								
								src/core/features/courses/pages/search/search.page.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								src/core/features/courses/pages/search/search.page.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,100 @@ | |||||||
|  | // (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<void> { | ||||||
|  |         this.currentSearch = text; | ||||||
|  |         this.courses = []; | ||||||
|  |         this.page = 0; | ||||||
|  |         this.total = 0; | ||||||
|  | 
 | ||||||
|  |         const modal = await CoreDomUtils.instance.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<void> { | ||||||
|  |         this.loadMoreError = false; | ||||||
|  | 
 | ||||||
|  |         try { | ||||||
|  |             const response = await CoreCourses.instance.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.instance.showErrorModalDefault(error, 'core.courses.errorsearching', true); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
| @ -39,7 +39,9 @@ | |||||||
|                         </ng-container> |                         </ng-container> | ||||||
|                     </ng-container> |                     </ng-container> | ||||||
|                 </ion-list> |                 </ion-list> | ||||||
|             <core-empty-box *ngIf="!hasContent" icon="qr-scanner" [message]="'core.course.nocontentavailable' | translate"></core-empty-box> |             <core-empty-box *ngIf="!hasContent" icon="qr-scanner" [message]="'core.course.nocontentavailable' | translate"> | ||||||
|  | 
 | ||||||
|  |             </core-empty-box> | ||||||
|         </core-loading> |         </core-loading> | ||||||
|     <!-- @todo </core-block-course-blocks> --> |     <!-- @todo </core-block-course-blocks> --> | ||||||
| </ion-content> | </ion-content> | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user