forked from EVOgeek/Vmeda.Online
		
	MOBILE-3594 sitehome: Add my courses page
This commit is contained in:
		
							parent
							
								
									38b11281a9
								
							
						
					
					
						commit
						cc6e87ea5c
					
				| @ -1,20 +1,25 @@ | |||||||
| <ng-container *ngIf="enabled && !(loading || status === statusDownloading)"> | <ng-container *ngIf="enabled && !loading"> | ||||||
|     <!-- Download button. --> |     <!-- Download button. --> | ||||||
|     <ion-button *ngIf="status == statusNotDownloaded" fill="clear" (click)="download($event, false)" color="dark" |     <ion-button *ngIf="status == statusNotDownloaded" fill="clear" (click)="download($event, false)" color="dark" | ||||||
|         class="core-animate-show-hide" [attr.aria-label]="'core.download' | translate"> |         class="core-animate-show-hide" [attr.aria-label]="(statusTranslatable || 'core.download') | translate"> | ||||||
|         <ion-icon slot="icon-only" name="cloud-download"></ion-icon> |         <ion-icon slot="icon-only" name="cloud-download"></ion-icon> | ||||||
|     </ion-button> |     </ion-button> | ||||||
| 
 | 
 | ||||||
|     <!-- Refresh button. --> |     <!-- Refresh button. --> | ||||||
|     <ion-button *ngIf="status == statusOutdated || (status == statusDownloaded && !canTrustDownload)" fill="clear" |     <ion-button *ngIf="status == statusOutdated || (status == statusDownloaded && !canTrustDownload)" fill="clear" | ||||||
|         (click)="download($event, true)" color="dark" class="core-animate-show-hide" [attr.aria-label]="'core.refresh' | translate"> |         (click)="download($event, true)" color="dark" class="core-animate-show-hide" | ||||||
|         <ion-icon slot="icon-only" name="fas-sync"></ion-icon> |         attr.aria-label]="(statusTranslatable || 'core.refresh') | translate"> | ||||||
|  |         <ion-icon slot="icon-only" name="fas-redo-alt"></ion-icon> | ||||||
|     </ion-button> |     </ion-button> | ||||||
| 
 | 
 | ||||||
|     <!-- Downloaded status icon. --> |     <!-- Downloaded status icon. --> | ||||||
|     <ion-icon *ngIf="status == statusDownloaded && canTrustDownload" class="core-icon-downloaded ion-padding-horizontal" color="success" |     <ion-icon *ngIf="status == statusDownloaded && canTrustDownload" class="core-icon-downloaded ion-padding-horizontal" | ||||||
|         name="cloud-done" [attr.aria-label]="'core.downloaded' | translate" role="status"></ion-icon> |         color="success" name="cloud-done" [attr.aria-label]="(statusTranslatable || 'core.downloaded') | translate" | ||||||
|  |         role="status"></ion-icon> | ||||||
|  | 
 | ||||||
|  |     <ion-spinner *ngIf="status === statusDownloading" class="core-animate-show-hide" | ||||||
|  |         [attr.aria-label]="(statusTranslatable || 'core.downloading') | translate"></ion-spinner> | ||||||
| </ng-container> | </ng-container> | ||||||
| 
 | 
 | ||||||
| <!-- Spinner. --> | <!-- Spinner. --> | ||||||
| <ion-spinner *ngIf="loading || status === statusDownloading" class="core-animate-show-hide"></ion-spinner> | <ion-spinner *ngIf="loading" class="core-animate-show-hide" [attr.aria-label]="'core.loading' | translate"></ion-spinner> | ||||||
|  | |||||||
| @ -19,7 +19,7 @@ import { CoreConstants } from '@/core/constants'; | |||||||
|  * Component to show a download button with refresh option, the spinner and the status of it. |  * Component to show a download button with refresh option, the spinner and the status of it. | ||||||
|  * |  * | ||||||
|  * Usage: |  * Usage: | ||||||
|  * <core-download-refresh [status]="status" enabled="true" canCheckUpdates="true" action="download()"></core-download-refresh> |  * <core-download-refresh [status]="status" enabled="true" canTrustDownload="true" action="download()"></core-download-refresh> | ||||||
|  */ |  */ | ||||||
| @Component({ | @Component({ | ||||||
|     selector: 'core-download-refresh', |     selector: 'core-download-refresh', | ||||||
| @ -29,6 +29,7 @@ import { CoreConstants } from '@/core/constants'; | |||||||
| export class CoreDownloadRefreshComponent { | export class CoreDownloadRefreshComponent { | ||||||
| 
 | 
 | ||||||
|     @Input() status?: string; // Download status.
 |     @Input() status?: string; // Download status.
 | ||||||
|  |     @Input() statusTranslatable?: string; // Download status translatable string.
 | ||||||
|     @Input() enabled = false; // Whether the download is enabled.
 |     @Input() enabled = false; // Whether the download is enabled.
 | ||||||
|     @Input() loading = true; // Force loading status when is not downloading.
 |     @Input() loading = true; // Force loading status when is not downloading.
 | ||||||
|     @Input() canTrustDownload = false; // If false, refresh will be shown if downloaded.
 |     @Input() canTrustDownload = false; // If false, refresh will be shown if downloaded.
 | ||||||
|  | |||||||
| @ -22,10 +22,14 @@ import { CoreDirectivesModule } from '@directives/directives.module'; | |||||||
| import { CorePipesModule } from '@pipes/pipes.module'; | import { CorePipesModule } from '@pipes/pipes.module'; | ||||||
| 
 | 
 | ||||||
| import { CoreCoursesCourseListItemComponent } from './course-list-item/course-list-item'; | import { CoreCoursesCourseListItemComponent } from './course-list-item/course-list-item'; | ||||||
|  | import { CoreCoursesCourseProgressComponent } from './course-progress/course-progress'; | ||||||
|  | import { CoreCoursesCourseOptionsMenuComponent } from './course-options-menu/course-options-menu'; | ||||||
| 
 | 
 | ||||||
| @NgModule({ | @NgModule({ | ||||||
|     declarations: [ |     declarations: [ | ||||||
|         CoreCoursesCourseListItemComponent, |         CoreCoursesCourseListItemComponent, | ||||||
|  |         CoreCoursesCourseProgressComponent, | ||||||
|  |         CoreCoursesCourseOptionsMenuComponent, | ||||||
|     ], |     ], | ||||||
|     imports: [ |     imports: [ | ||||||
|         CommonModule, |         CommonModule, | ||||||
| @ -35,10 +39,13 @@ import { CoreCoursesCourseListItemComponent } from './course-list-item/course-li | |||||||
|         CoreDirectivesModule, |         CoreDirectivesModule, | ||||||
|         CorePipesModule, |         CorePipesModule, | ||||||
|     ], |     ], | ||||||
|     providers: [ |  | ||||||
|     ], |  | ||||||
|     exports: [ |     exports: [ | ||||||
|         CoreCoursesCourseListItemComponent, |         CoreCoursesCourseListItemComponent, | ||||||
|  |         CoreCoursesCourseProgressComponent, | ||||||
|  |         CoreCoursesCourseOptionsMenuComponent, | ||||||
|  |     ], | ||||||
|  |     entryComponents: [ | ||||||
|  |         CoreCoursesCourseOptionsMenuComponent, | ||||||
|     ], |     ], | ||||||
| }) | }) | ||||||
| export class CoreCoursesComponentsModule {} | export class CoreCoursesComponentsModule {} | ||||||
|  | |||||||
| @ -0,0 +1,26 @@ | |||||||
|  | <ion-item button class="ion-text-wrap" (click)="action('download')" *ngIf="downloadCourseEnabled"> | ||||||
|  |     <ion-icon *ngIf="!prefetch.loading" [name]="prefetch.icon" slot="start"></ion-icon> | ||||||
|  |     <ion-spinner *ngIf="prefetch.loading" slot="start"></ion-spinner> | ||||||
|  |     <ion-label><h2>{{ prefetch.statusTranslatable | translate }}</h2></ion-label> | ||||||
|  | </ion-item> | ||||||
|  | <ion-item button class="ion-text-wrap" (click)="action('delete')" *ngIf="prefetch.status == 'downloaded' || prefetch.status == 'outdated'"> | ||||||
|  |     <ion-icon name="fas-trash" slot="start"></ion-icon> | ||||||
|  |     <ion-label><h2>{{ 'addon.storagemanager.deletecourse' | translate }}</h2></ion-label> | ||||||
|  | </ion-item> | ||||||
|  | <ion-item button class="ion-text-wrap" (click)="action('hide')" *ngIf="!course.hidden"> | ||||||
|  |     <ion-icon name="fas-eye" slot="start"></ion-icon> | ||||||
|  |     <ion-label><h2>{{ 'core.courses.hidecourse' | translate }}</h2></ion-label> | ||||||
|  | </ion-item> | ||||||
|  | <ion-item button class="ion-text-wrap" (click)="action('show')" *ngIf="course.hidden"> | ||||||
|  |     <ion-icon name="fas-eye-slash" slot="start"></ion-icon> | ||||||
|  |     <ion-label><h2>{{ 'core.courses.show' | translate }}</h2></ion-label> | ||||||
|  | </ion-item> | ||||||
|  | <ion-item button class="ion-text-wrap" (click)="action('favourite')" *ngIf="!course.isfavourite"> | ||||||
|  |     <ion-icon name="fas-star" slot="start"></ion-icon> | ||||||
|  |     <ion-label><h2>{{ 'core.courses.addtofavourites' | translate }}</h2></ion-label> | ||||||
|  | </ion-item> | ||||||
|  | <ion-item button class="ion-text-wrap" (click)="action('unfavourite')" *ngIf="course.isfavourite"> | ||||||
|  |     <ion-icon name="far-star" slot="start"></ion-icon> | ||||||
|  |     <ion-label><h2>{{ 'core.courses.removefromfavourites' | translate }}</h2></ion-label> | ||||||
|  | </ion-item> | ||||||
|  | 
 | ||||||
| @ -0,0 +1,59 @@ | |||||||
|  | // (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 { NavParams, PopoverController } from '@ionic/angular'; | ||||||
|  | import { CoreCourses } from '../../services/courses'; | ||||||
|  | import { CoreEnrolledCourseDataWithExtraInfoAndOptions } from '../../services/courses.helper'; | ||||||
|  | import { CorePrefetchStatusInfo } from '@features/course/services/course.helper'; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * This component is meant to display a popover with the course options. | ||||||
|  |  */ | ||||||
|  | @Component({ | ||||||
|  |     selector: 'core-courses-course-options-menu', | ||||||
|  |     templateUrl: 'core-courses-course-options-menu.html', | ||||||
|  | }) | ||||||
|  | export class CoreCoursesCourseOptionsMenuComponent implements OnInit { | ||||||
|  | 
 | ||||||
|  |     course!: CoreEnrolledCourseDataWithExtraInfoAndOptions; // The course.
 | ||||||
|  |     prefetch!: CorePrefetchStatusInfo; // The prefecth info.
 | ||||||
|  | 
 | ||||||
|  |     downloadCourseEnabled = false; | ||||||
|  | 
 | ||||||
|  |     constructor( | ||||||
|  |         navParams: NavParams, | ||||||
|  |         protected popoverController: PopoverController, | ||||||
|  |     ) { | ||||||
|  |         this.course = navParams.get('course') || {}; | ||||||
|  |         this.prefetch = navParams.get('prefetch') || {}; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Component being initialized. | ||||||
|  |      */ | ||||||
|  |     ngOnInit(): void { | ||||||
|  |         this.downloadCourseEnabled = !CoreCourses.instance.isDownloadCourseDisabledInSite(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Do an action over the course. | ||||||
|  |      * | ||||||
|  |      * @param action Action name to take. | ||||||
|  |      */ | ||||||
|  |     action(action: string): void { | ||||||
|  |         this.popoverController.dismiss(action); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
| @ -0,0 +1,57 @@ | |||||||
|  | <ion-card [attr.course-color]="course.color ? null : course.colorNumber"> | ||||||
|  |     <div (click)="openCourse()" class="core-course-thumb" [class.core-course-color-img]="course.courseImage" | ||||||
|  |         [style.background-color]="course.color"> | ||||||
|  |         <img *ngIf="course.courseImage" [src]="course.courseImage" core-external-content alt=""/> | ||||||
|  |     </div> | ||||||
|  |     <ion-item button lines="none" (click)="openCourse()" [title]="course.displayname || course.fullname" | ||||||
|  |         class="core-course-header" [class.item-disabled]="course.visible == 0" | ||||||
|  |         [class.core-course-more-than-title]="(course.progress != null && course.progress! >= 0)"> | ||||||
|  |         <ion-label | ||||||
|  |             class="ion-text-wrap core-course-title" | ||||||
|  |             [class.core-course-with-buttons]="courseOptionMenuEnabled || (downloadCourseEnabled && showDownload)" | ||||||
|  |             [class.core-course-with-spinner]="(downloadCourseEnabled && prefetchCourseData.icon == 'spinner') || showSpinner"> | ||||||
|  |             <p *ngIf="course.categoryname || (course.displayname && course.shortname && course.fullname != course.displayname)" | ||||||
|  |                 class="core-course-additional-info"> | ||||||
|  |                 <span *ngIf="course.categoryname" class="core-course-category"> | ||||||
|  |                     <core-format-text [text]="course.categoryname"></core-format-text> | ||||||
|  |                 </span> | ||||||
|  |                 <span *ngIf="course.categoryname && course.displayname && course.shortname && course.fullname != course.displayname" | ||||||
|  |                     class="core-course-category"> | </span> | ||||||
|  |                 <span *ngIf="course.displayname && course.shortname && course.fullname != course.displayname" | ||||||
|  |                     class="core-course-shortname"> | ||||||
|  |                     <core-format-text [text]="course.shortname" contextLevel="course" [contextInstanceId]="course.id"> | ||||||
|  |                     </core-format-text> | ||||||
|  |                 </span> | ||||||
|  |             </p> | ||||||
|  |             <h2> | ||||||
|  |                 <ion-icon name="fas-star" *ngIf="course.isfavourite"></ion-icon> | ||||||
|  |                 <core-format-text [text]="course.fullname" contextLevel="course" [contextInstanceId]="course.id"></core-format-text> | ||||||
|  |             </h2> | ||||||
|  |         </ion-label> | ||||||
|  | 
 | ||||||
|  |         <div class="core-button-spinner" *ngIf="downloadCourseEnabled && !courseOptionMenuEnabled && showDownload" slot="end"> | ||||||
|  |             <core-download-refresh | ||||||
|  |                 [status]="prefetchCourseData.status" | ||||||
|  |                 [enabled]="downloadCourseEnabled" | ||||||
|  |                 [statusTranslatable]="prefetchCourseData.statusTranslatable" | ||||||
|  |                 canTrustDownload="false" | ||||||
|  |                 [loading]="prefetchCourseData.loading" | ||||||
|  |                 action="prefetchCourse()"></core-download-refresh> | ||||||
|  |         </div> | ||||||
|  | 
 | ||||||
|  |         <div class="core-button-spinner" *ngIf="courseOptionMenuEnabled"  slot="end"> | ||||||
|  |             <!-- Download course spinner. --> | ||||||
|  |             <ion-spinner *ngIf="(downloadCourseEnabled && prefetchCourseData.icon == 'spinner') || showSpinner"></ion-spinner> | ||||||
|  | 
 | ||||||
|  |             <!-- Options menu. --> | ||||||
|  |             <ion-button fill="clear" color="dark" (click)="showCourseOptionsMenu($event)" *ngIf="!showSpinner" | ||||||
|  |                 [attr.aria-label]="('core.displayoptions' | translate)"> | ||||||
|  |                 <ion-icon name="ellipsis-vertical" slot="icon-only"></ion-icon> | ||||||
|  |             </ion-button> | ||||||
|  |         </div> | ||||||
|  |     </ion-item> | ||||||
|  |     <ion-item *ngIf="showAll && course.progress != null && course.progress! >= 0 && course.completionusertracked !== false" lines="none"> | ||||||
|  |         <ion-label><core-progress-bar [progress]="course.progress"></core-progress-bar></ion-label> | ||||||
|  |     </ion-item> | ||||||
|  |     <ng-content></ng-content> | ||||||
|  | </ion-card> | ||||||
| @ -0,0 +1,163 @@ | |||||||
|  | :host { | ||||||
|  |     ion-card { | ||||||
|  |         display: flex; | ||||||
|  |         flex-direction: column; | ||||||
|  |         align-self: stretch; | ||||||
|  |         height: calc(100% - 20px); | ||||||
|  | 
 | ||||||
|  |         &[course-color="0"] .core-course-thumb { | ||||||
|  |             background: var(--core-course-image-background-0); | ||||||
|  |         } | ||||||
|  |         &[course-color="1"] .core-course-thumb { | ||||||
|  |             background: var(--core-course-image-background-1); | ||||||
|  |         } | ||||||
|  |         &[course-color="2"] .core-course-thumb { | ||||||
|  |             background: var(--core-course-image-background-2); | ||||||
|  |         } | ||||||
|  |         &[course-color="3"] .core-course-thumb { | ||||||
|  |             background: var(--core-course-image-background-3); | ||||||
|  |         } | ||||||
|  |         &[course-color="4"] .core-course-thumb { | ||||||
|  |             background: var(--core-course-image-background-4); | ||||||
|  |         } | ||||||
|  |         &[course-color="5"] .core-course-thumb { | ||||||
|  |             background: var(--core-course-image-background-5); | ||||||
|  |         } | ||||||
|  |         &[course-color="6"] .core-course-thumb { | ||||||
|  |             background: var(--core-course-image-background-6); | ||||||
|  |         } | ||||||
|  |         &[course-color="7"] .core-course-thumb { | ||||||
|  |             background: var(--core-course-image-background-7); | ||||||
|  |         } | ||||||
|  |         &[course-color="8"] .core-course-thumb { | ||||||
|  |             background: var(--core-course-image-background-8); | ||||||
|  |         } | ||||||
|  |         &[course-color="9"] .core-course-thumb { | ||||||
|  |             background: var(--core-course-image-background-9); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         .core-course-thumb { | ||||||
|  |             padding-top: 40%; | ||||||
|  |             width: 100%; | ||||||
|  |             overflow: hidden; | ||||||
|  |             cursor: pointer; | ||||||
|  |             pointer-events: auto; | ||||||
|  |             position: relative; | ||||||
|  |             background-position: center; | ||||||
|  |             background-size: cover; | ||||||
|  |             -webkit-transition: all 50ms ease-in-out; | ||||||
|  |             transition: all 50ms ease-in-out; | ||||||
|  | 
 | ||||||
|  |             &.core-course-color-img { | ||||||
|  |                 background: white; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             img { | ||||||
|  |                 position: absolute; | ||||||
|  |                 top: 0; | ||||||
|  |                 bottom: 0; | ||||||
|  |                 margin: auto; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         .core-course-additional-info { | ||||||
|  |             margin-bottom: 8px; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         .core-course-header { | ||||||
|  |             padding-top: 8px; | ||||||
|  |             padding-bottom: 8px; | ||||||
|  |             .core-course-title { | ||||||
|  |                 margin: 5px 0; | ||||||
|  |                 flex-grow: 1; | ||||||
|  | 
 | ||||||
|  |                 h2 ion-icon { | ||||||
|  |                     margin-right: 4px; | ||||||
|  |                     color: var(--core-star-color); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             &.core-course-more-than-title { | ||||||
|  |                 padding-bottom: 0; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             .core-button-spinner { | ||||||
|  |                 margin: 0; | ||||||
|  |             } | ||||||
|  |             .core-button-spinner ion-spinner { | ||||||
|  |                 vertical-align: top; // the better option for most scenarios | ||||||
|  |                 vertical-align: -webkit-baseline-middle; // the best for those that support it | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             .core-button-spinner .core-icon-downloaded { | ||||||
|  |                 font-size: 28.8px; | ||||||
|  |                 margin-top: 8px; | ||||||
|  |                 vertical-align: top; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             .item-button[icon-only] { | ||||||
|  |                 min-width: 50px; | ||||||
|  |                 width: 50px; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     button { | ||||||
|  |         z-index: 1; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // @todo | ||||||
|  | :host-context(.core-horizontal-scroll) { | ||||||
|  |     /*@include horizontal_scroll_item(80%, 250px, 300px);*/ | ||||||
|  | 
 | ||||||
|  |     ion-card { | ||||||
|  |         .core-course-thumb { | ||||||
|  |             padding-top: 30%; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         .core-course-link { | ||||||
|  |             /*@include padding(4px, 0px, 4px, 8px);*/ | ||||||
|  |             .core-course-additional-info { | ||||||
|  |                 font-size: 1.2rem; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             .core-course-title { | ||||||
|  |                 margin: 3px 0; | ||||||
|  | 
 | ||||||
|  |                 h2 { | ||||||
|  |                     font-size: 1.5rem; | ||||||
|  |                     ion-icon { | ||||||
|  |                         margin-right: 2px; | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 &.core-course-with-buttons { | ||||||
|  |                     max-width: calc(100% - 40px); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             .core-button-spinner { | ||||||
|  |                 min-height: 40px; | ||||||
|  |                 min-width: 40px; | ||||||
|  | 
 | ||||||
|  |                 ion-spinner { | ||||||
|  |                     width: 20px; | ||||||
|  |                     height: 20px; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             .item-button[icon-only] { | ||||||
|  |                 min-width: 40px; | ||||||
|  |                 width: 40px; | ||||||
|  |                 font-size: 1.5rem; | ||||||
|  |                 padding: 8px; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | :host-context(body.version-3-1) { | ||||||
|  |     .core-course-thumb{ | ||||||
|  |         display: none; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,283 @@ | |||||||
|  | // (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, OnDestroy } from '@angular/core'; | ||||||
|  | import { PopoverController } from '@ionic/angular'; | ||||||
|  | import { CoreEventCourseStatusChanged, CoreEventObserver, CoreEvents } from '@singletons/events'; | ||||||
|  | import { CoreSites } from '@services/sites'; | ||||||
|  | import { CoreDomUtils } from '@services/utils/dom'; | ||||||
|  | // import { CoreUserProvider } from '@core/user/providers/user';
 | ||||||
|  | import { CoreCourses } from '@features/courses/services/courses'; | ||||||
|  | import { CoreCourse, CoreCourseProvider } from '@features/course/services/course'; | ||||||
|  | import { CoreCourseHelper, CorePrefetchStatusInfo } from '@features/course/services/course.helper'; | ||||||
|  | import { Translate } from '@singletons/core.singletons'; | ||||||
|  | import { CoreConstants } from '@/core/constants'; | ||||||
|  | import { CoreEnrolledCourseDataWithExtraInfoAndOptions } from '../../services/courses.helper'; | ||||||
|  | import { CoreCoursesCourseOptionsMenuComponent } from '../course-options-menu/course-options-menu'; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * This component is meant to display a course for a list of courses with progress. | ||||||
|  |  * | ||||||
|  |  * Example usage: | ||||||
|  |  * | ||||||
|  |  * <core-courses-course-progress [course]="course"> | ||||||
|  |  * </core-courses-course-progress> | ||||||
|  |  */ | ||||||
|  | @Component({ | ||||||
|  |     selector: 'core-courses-course-progress', | ||||||
|  |     templateUrl: 'core-courses-course-progress.html', | ||||||
|  |     styleUrls: ['course-progress.scss'], | ||||||
|  | }) | ||||||
|  | export class CoreCoursesCourseProgressComponent implements OnInit, OnDestroy { | ||||||
|  | 
 | ||||||
|  |     @Input() course!: CoreEnrolledCourseDataWithExtraInfoAndOptions; // The course to render.
 | ||||||
|  |     @Input() showAll = false; // If true, will show all actions, options, star and progress.
 | ||||||
|  |     @Input() showDownload = true; // If true, will show download button. Only works if the options menu is not shown.
 | ||||||
|  | 
 | ||||||
|  |     courseStatus = CoreConstants.NOT_DOWNLOADED; | ||||||
|  |     isDownloading = false; | ||||||
|  |     prefetchCourseData: CorePrefetchStatusInfo = { | ||||||
|  |         icon: '', | ||||||
|  |         statusTranslatable: 'core.loading', | ||||||
|  |         status: '', | ||||||
|  |         loading: true, | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     showSpinner = false; | ||||||
|  |     downloadCourseEnabled = false; | ||||||
|  |     courseOptionMenuEnabled = false; | ||||||
|  | 
 | ||||||
|  |     protected isDestroyed = false; | ||||||
|  |     protected courseStatusObserver?: CoreEventObserver; | ||||||
|  |     protected siteUpdatedObserver?: CoreEventObserver; | ||||||
|  | 
 | ||||||
|  |     constructor( | ||||||
|  |         protected popoverCtrl: PopoverController, | ||||||
|  |     ) { } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Component being initialized. | ||||||
|  |      */ | ||||||
|  |     ngOnInit(): void { | ||||||
|  | 
 | ||||||
|  |         this.downloadCourseEnabled = !CoreCourses.instance.isDownloadCourseDisabledInSite(); | ||||||
|  | 
 | ||||||
|  |         if (this.downloadCourseEnabled) { | ||||||
|  |             this.initPrefetchCourse(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // This field is only available from 3.6 onwards.
 | ||||||
|  |         this.courseOptionMenuEnabled = this.showAll && typeof this.course.isfavourite != 'undefined'; | ||||||
|  | 
 | ||||||
|  |         // Refresh the enabled flag if site is updated.
 | ||||||
|  |         this.siteUpdatedObserver = CoreEvents.on(CoreEvents.SITE_UPDATED, () => { | ||||||
|  |             const wasEnabled = this.downloadCourseEnabled; | ||||||
|  | 
 | ||||||
|  |             this.downloadCourseEnabled = !CoreCourses.instance.isDownloadCourseDisabledInSite(); | ||||||
|  | 
 | ||||||
|  |             if (!wasEnabled && this.downloadCourseEnabled) { | ||||||
|  |                 // Download course is enabled now, initialize it.
 | ||||||
|  |                 this.initPrefetchCourse(); | ||||||
|  |             } | ||||||
|  |         }, CoreSites.instance.getCurrentSiteId()); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Initialize prefetch course. | ||||||
|  |      */ | ||||||
|  |     async initPrefetchCourse(): Promise<void> { | ||||||
|  |         if (typeof this.courseStatusObserver != 'undefined') { | ||||||
|  |             // Already initialized.
 | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // Listen for status change in course.
 | ||||||
|  |         this.courseStatusObserver = CoreEvents.on(CoreEvents.COURSE_STATUS_CHANGED, (data: CoreEventCourseStatusChanged) => { | ||||||
|  |             if (data.courseId == this.course.id || data.courseId == CoreCourseProvider.ALL_COURSES_CLEARED) { | ||||||
|  |                 this.updateCourseStatus(data.status); | ||||||
|  |             } | ||||||
|  |         }, CoreSites.instance.getCurrentSiteId()); | ||||||
|  | 
 | ||||||
|  |         // Determine course prefetch icon.
 | ||||||
|  |         const status = await CoreCourse.instance.getCourseStatus(this.course.id); | ||||||
|  | 
 | ||||||
|  |         this.prefetchCourseData = CoreCourseHelper.instance.getCourseStatusIconAndTitleFromStatus(status); | ||||||
|  |         this.courseStatus = status; | ||||||
|  | 
 | ||||||
|  |         if (this.prefetchCourseData.loading) { | ||||||
|  |             // Course is being downloaded. Get the download promise.
 | ||||||
|  |             const promise = CoreCourseHelper.instance.getCourseDownloadPromise(this.course.id); | ||||||
|  |             if (promise) { | ||||||
|  |                 // There is a download promise. If it fails, show an error.
 | ||||||
|  |                 promise.catch((error) => { | ||||||
|  |                     if (!this.isDestroyed) { | ||||||
|  |                         CoreDomUtils.instance.showErrorModalDefault(error, 'core.course.errordownloadingcourse', true); | ||||||
|  |                     } | ||||||
|  |                 }); | ||||||
|  |             } else { | ||||||
|  |                 // No download, this probably means that the app was closed while downloading. Set previous status.
 | ||||||
|  |                 CoreCourse.instance.setCoursePreviousStatus(this.course.id); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Open a course. | ||||||
|  |      */ | ||||||
|  |     openCourse(): void { | ||||||
|  |         CoreCourseHelper.instance.openCourse(this.course); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Prefetch the course. | ||||||
|  |      * | ||||||
|  |      * @param e Click event. | ||||||
|  |      */ | ||||||
|  |     prefetchCourse(e: Event): void { | ||||||
|  |         e.preventDefault(); | ||||||
|  |         e.stopPropagation(); | ||||||
|  | 
 | ||||||
|  |         /* @ todo try { | ||||||
|  |             CoreCourseHelper.instance.confirmAndPrefetchCourse(this.prefetchCourseData, this.course); | ||||||
|  |         } catch (error) { | ||||||
|  |             if (!this.isDestroyed) { | ||||||
|  |                 CoreDomUtils.instance.showErrorModalDefault(error, 'core.course.errordownloadingcourse', true); | ||||||
|  |             } | ||||||
|  |         }*/ | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Delete the course. | ||||||
|  |      */ | ||||||
|  |     async deleteCourse(): Promise<void> { | ||||||
|  |         try { | ||||||
|  |             await CoreDomUtils.instance.showDeleteConfirm('core.course.confirmdeletestoreddata'); | ||||||
|  |         } catch (error) { | ||||||
|  |             if (CoreDomUtils.instance.isCanceledError(error)) { | ||||||
|  |                 throw error; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         const modal = await CoreDomUtils.instance.showModalLoading(); | ||||||
|  | 
 | ||||||
|  |         try { | ||||||
|  |             await CoreCourseHelper.instance.deleteCourseFiles(this.course.id); | ||||||
|  |         } catch (error) { | ||||||
|  |             CoreDomUtils.instance.showErrorModalDefault(error, Translate.instance.instant('core.errordeletefile')); | ||||||
|  |         } finally { | ||||||
|  |             modal.dismiss(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Update the course status icon and title. | ||||||
|  |      * | ||||||
|  |      * @param status Status to show. | ||||||
|  |      */ | ||||||
|  |     protected updateCourseStatus(status: string): void { | ||||||
|  |         this.prefetchCourseData = CoreCourseHelper.instance.getCourseStatusIconAndTitleFromStatus(status); | ||||||
|  | 
 | ||||||
|  |         this.courseStatus = status; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Show the context menu. | ||||||
|  |      * | ||||||
|  |      * @param e Click Event. | ||||||
|  |      * @todo | ||||||
|  |      */ | ||||||
|  |     async showCourseOptionsMenu(e: Event): Promise<void> { | ||||||
|  |         e.preventDefault(); | ||||||
|  |         e.stopPropagation(); | ||||||
|  | 
 | ||||||
|  |         const popover = await this.popoverCtrl.create({ | ||||||
|  |             component: CoreCoursesCourseOptionsMenuComponent, | ||||||
|  |             componentProps: { | ||||||
|  |                 course: this.course, | ||||||
|  |                 courseStatus: this.courseStatus, | ||||||
|  |                 prefetch: this.prefetchCourseData, | ||||||
|  |             }, | ||||||
|  |             event: e, | ||||||
|  |         }); | ||||||
|  |         popover.present(); | ||||||
|  | 
 | ||||||
|  |         const action = await popover.onDidDismiss<string>(); | ||||||
|  | 
 | ||||||
|  |         if (action.data) { | ||||||
|  |             switch (action.data) { | ||||||
|  |                 case 'download': | ||||||
|  |                     if (!this.prefetchCourseData.loading) { | ||||||
|  |                         this.prefetchCourse(e); | ||||||
|  |                     } | ||||||
|  |                     break; | ||||||
|  |                 case 'delete': | ||||||
|  |                     if (this.courseStatus == 'downloaded' || this.courseStatus == 'outdated') { | ||||||
|  |                         this.deleteCourse(); | ||||||
|  |                     } | ||||||
|  |                     break; | ||||||
|  |                 case 'hide': | ||||||
|  |                     this.setCourseHidden(true); | ||||||
|  |                     break; | ||||||
|  |                 case 'show': | ||||||
|  |                     this.setCourseHidden(false); | ||||||
|  |                     break; | ||||||
|  |                 case 'favourite': | ||||||
|  |                     this.setCourseFavourite(true); | ||||||
|  |                     break; | ||||||
|  |                 case 'unfavourite': | ||||||
|  |                     this.setCourseFavourite(false); | ||||||
|  |                     break; | ||||||
|  |                 default: | ||||||
|  |                     break; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Hide/Unhide the course from the course list. | ||||||
|  |      * | ||||||
|  |      * @param hide True to hide and false to show. | ||||||
|  |      * @todo | ||||||
|  |      */ | ||||||
|  |     // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | ||||||
|  |     protected setCourseHidden(hide: boolean): void { | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Favourite/Unfavourite the course from the course list. | ||||||
|  |      * | ||||||
|  |      * @param favourite True to favourite and false to unfavourite. | ||||||
|  |      * @todo | ||||||
|  |      */ | ||||||
|  |     // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | ||||||
|  |     protected setCourseFavourite(favourite: boolean): void { | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Component destroyed. | ||||||
|  |      */ | ||||||
|  |     ngOnDestroy(): void { | ||||||
|  |         this.isDestroyed = true; | ||||||
|  | 
 | ||||||
|  |         this.siteUpdatedObserver?.off(); | ||||||
|  |         this.courseStatusObserver?.off(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
| @ -32,7 +32,7 @@ const routes: Routes = [ | |||||||
|         children: [ |         children: [ | ||||||
|             { |             { | ||||||
|                 path: '', |                 path: '', | ||||||
|                 redirectTo: 'all', |                 redirectTo: 'my', | ||||||
|                 pathMatch: 'full', |                 pathMatch: 'full', | ||||||
|             }, |             }, | ||||||
|             { |             { | ||||||
| @ -57,6 +57,12 @@ const routes: Routes = [ | |||||||
|                     import('@features/courses/pages/search/search.page.module') |                     import('@features/courses/pages/search/search.page.module') | ||||||
|                         .then(m => m.CoreCoursesSearchPageModule), |                         .then(m => m.CoreCoursesSearchPageModule), | ||||||
|             }, |             }, | ||||||
|  |             { | ||||||
|  |                 path: 'my', | ||||||
|  |                 loadChildren: () => | ||||||
|  |                     import('@features/courses/pages/my-courses/my-courses.page.module') | ||||||
|  |                         .then(m => m.CoreCoursesMyCoursesPageModule), | ||||||
|  |             }, | ||||||
|         ], |         ], | ||||||
|     }, |     }, | ||||||
| ]; | ]; | ||||||
|  | |||||||
							
								
								
									
										48
									
								
								src/core/features/courses/pages/my-courses/my-courses.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								src/core/features/courses/pages/my-courses/my-courses.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,48 @@ | |||||||
|  | <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.mycourses' | translate }}</ion-title> | ||||||
|  | 
 | ||||||
|  |         <ion-buttons slot="end"> | ||||||
|  |             <ion-button *ngIf="searchEnabled" (click)="openSearch()" [attr.aria-label]="'core.courses.searchcourses' | translate"> | ||||||
|  |                 <ion-icon name="search" slot="icon-only"></ion-icon> | ||||||
|  |             </ion-button> | ||||||
|  |             <ng-container *ngIf="downloadAllCoursesEnabled && courses && courses.length >= 2"> | ||||||
|  |                 <ion-button *ngIf="!downloadAllCoursesLoading" (click)="prefetchCourses()" | ||||||
|  |                 [attr.aria-label]="'core.courses.downloadcourses' | translate"> | ||||||
|  |                     <ion-icon [name]="downloadAllCoursesIcon" slot="icon-only"></ion-icon> | ||||||
|  |                 </ion-button> | ||||||
|  |                 <ion-spinner *ngIf="downloadAllCoursesBadge == '' && downloadAllCoursesLoading" | ||||||
|  |                 [attr.aria-label]="'core.loading' | translate"></ion-spinner> | ||||||
|  |                 <ion-badge *ngIf="downloadAllCoursesBadge != '' && downloadAllCoursesLoading" | ||||||
|  |                 [attr.aria-label]="'core.downloading' | translate">{{downloadAllCoursesBadge}}</ion-badge> | ||||||
|  |             </ng-container> | ||||||
|  |         </ion-buttons> | ||||||
|  |     </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"> | ||||||
|  |         <ion-searchbar #searchbar *ngIf="courses && courses.length > 5" [(ngModel)]="filter" (ionInput)="filterChanged($event)" | ||||||
|  |             (ionCancel)="filterChanged()" [placeholder]="'core.courses.filtermycourses' | translate"> | ||||||
|  |         </ion-searchbar> | ||||||
|  |         <ion-grid class="ion-no-padding safe-area-page"> | ||||||
|  |             <ion-row class="ion-no-padding"> | ||||||
|  |                 <ion-col *ngFor="let course of filteredCourses" class="ion-no-padding" size="12" size-sm="6" size-md="6" | ||||||
|  |                     size-lg="4" size-xl="4" align-self-stretch> | ||||||
|  |                     <core-courses-course-progress [course]="course" class="core-courseoverview" showAll="true"> | ||||||
|  |                     </core-courses-course-progress> | ||||||
|  |                 </ion-col> | ||||||
|  |             </ion-row> | ||||||
|  |         </ion-grid> | ||||||
|  |         <core-empty-box *ngIf="!courses || !courses.length" icon="fas-graduation-cap" | ||||||
|  |             [message]="'core.courses.nocourses' | translate"> | ||||||
|  |             <p *ngIf="searchEnabled">{{ 'core.courses.searchcoursesadvice' | translate }}</p> | ||||||
|  |         </core-empty-box> | ||||||
|  |     </core-loading> | ||||||
|  | </ion-content> | ||||||
| @ -0,0 +1,51 @@ | |||||||
|  | // (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 { FormsModule } from '@angular/forms'; | ||||||
|  | 
 | ||||||
|  | import { CoreComponentsModule } from '@components/components.module'; | ||||||
|  | import { CoreDirectivesModule } from '@directives/directives.module'; | ||||||
|  | 
 | ||||||
|  | import { CoreCoursesMyCoursesPage } from './my-courses.page'; | ||||||
|  | import { CoreCoursesComponentsModule } from '../../components/components.module'; | ||||||
|  | 
 | ||||||
|  | const routes: Routes = [ | ||||||
|  |     { | ||||||
|  |         path: '', | ||||||
|  |         component: CoreCoursesMyCoursesPage, | ||||||
|  |     }, | ||||||
|  | ]; | ||||||
|  | 
 | ||||||
|  | @NgModule({ | ||||||
|  |     imports: [ | ||||||
|  |         RouterModule.forChild(routes), | ||||||
|  |         CommonModule, | ||||||
|  |         FormsModule, | ||||||
|  |         IonicModule, | ||||||
|  |         TranslateModule.forChild(), | ||||||
|  |         CoreComponentsModule, | ||||||
|  |         CoreDirectivesModule, | ||||||
|  |         CoreCoursesComponentsModule, | ||||||
|  |     ], | ||||||
|  |     declarations: [ | ||||||
|  |         CoreCoursesMyCoursesPage, | ||||||
|  |     ], | ||||||
|  |     exports: [RouterModule], | ||||||
|  | }) | ||||||
|  | export class CoreCoursesMyCoursesPageModule { } | ||||||
							
								
								
									
										215
									
								
								src/core/features/courses/pages/my-courses/my-courses.page.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										215
									
								
								src/core/features/courses/pages/my-courses/my-courses.page.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,215 @@ | |||||||
|  | // (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, OnDestroy, ViewChild } from '@angular/core'; | ||||||
|  | import { NavController, IonSearchbar, IonRefresher } from '@ionic/angular'; | ||||||
|  | import { CoreEventObserver, CoreEvents } from '@singletons/events'; | ||||||
|  | import { CoreSites } from '@services/sites'; | ||||||
|  | import { CoreDomUtils } from '@services/utils/dom'; | ||||||
|  | import { | ||||||
|  |     CoreCoursesProvider, | ||||||
|  |     CoreCoursesMyCoursesUpdatedEventData, | ||||||
|  |     CoreCourses, | ||||||
|  | } from '../../services/courses'; | ||||||
|  | import { CoreCoursesHelper, CoreEnrolledCourseDataWithExtraInfoAndOptions } from '../../services/courses.helper'; | ||||||
|  | import { CoreCourseHelper } from '@features/course/services/course.helper'; | ||||||
|  | import { CoreConstants } from '@/core/constants'; | ||||||
|  | // import { CoreCourseOptionsDelegate } from '@core/course/services/options-delegate';
 | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Page that displays the list of courses the user is enrolled in. | ||||||
|  |  */ | ||||||
|  | @Component({ | ||||||
|  |     selector: 'page-core-courses-my-courses', | ||||||
|  |     templateUrl: 'my-courses.html', | ||||||
|  | }) | ||||||
|  | export class CoreCoursesMyCoursesPage implements OnInit, OnDestroy { | ||||||
|  | 
 | ||||||
|  |     @ViewChild(IonSearchbar) searchbar!: IonSearchbar; | ||||||
|  | 
 | ||||||
|  |     courses: CoreEnrolledCourseDataWithExtraInfoAndOptions[] = []; | ||||||
|  |     filteredCourses: CoreEnrolledCourseDataWithExtraInfoAndOptions[] = []; | ||||||
|  |     searchEnabled = false; | ||||||
|  |     filter = ''; | ||||||
|  |     showFilter = false; | ||||||
|  |     coursesLoaded = false; | ||||||
|  |     downloadAllCoursesIcon = CoreConstants.NOT_DOWNLOADED_ICON; | ||||||
|  |     downloadAllCoursesLoading = false; | ||||||
|  |     downloadAllCoursesBadge = ''; | ||||||
|  |     downloadAllCoursesEnabled = false; | ||||||
|  | 
 | ||||||
|  |     protected myCoursesObserver: CoreEventObserver; | ||||||
|  |     protected siteUpdatedObserver: CoreEventObserver; | ||||||
|  |     protected isDestroyed = false; | ||||||
|  |     protected courseIds = ''; | ||||||
|  | 
 | ||||||
|  |     constructor( | ||||||
|  |         protected navCtrl: NavController, | ||||||
|  |     ) { | ||||||
|  |         // Update list if user enrols in a course.
 | ||||||
|  |         this.myCoursesObserver = CoreEvents.on( | ||||||
|  |             CoreCoursesProvider.EVENT_MY_COURSES_UPDATED, | ||||||
|  |             (data: CoreCoursesMyCoursesUpdatedEventData) => { | ||||||
|  | 
 | ||||||
|  |                 if (data.action == CoreCoursesProvider.ACTION_ENROL) { | ||||||
|  |                     this.fetchCourses(); | ||||||
|  |                 } | ||||||
|  |             }, | ||||||
|  | 
 | ||||||
|  |             CoreSites.instance.getCurrentSiteId(), | ||||||
|  |         ); | ||||||
|  | 
 | ||||||
|  |         // Refresh the enabled flags if site is updated.
 | ||||||
|  |         this.siteUpdatedObserver = CoreEvents.on(CoreEvents.SITE_UPDATED, () => { | ||||||
|  |             this.searchEnabled = !CoreCourses.instance.isSearchCoursesDisabledInSite(); | ||||||
|  |             this.downloadAllCoursesEnabled = !CoreCourses.instance.isDownloadCoursesDisabledInSite(); | ||||||
|  |         }, CoreSites.instance.getCurrentSiteId()); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Component being initialized. | ||||||
|  |      */ | ||||||
|  |     ngOnInit(): void { | ||||||
|  |         this.searchEnabled = !CoreCourses.instance.isSearchCoursesDisabledInSite(); | ||||||
|  |         this.downloadAllCoursesEnabled = !CoreCourses.instance.isDownloadCoursesDisabledInSite(); | ||||||
|  | 
 | ||||||
|  |         this.fetchCourses().finally(() => { | ||||||
|  |             this.coursesLoaded = true; | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Fetch the user courses. | ||||||
|  |      * | ||||||
|  |      * @return Promise resolved when done. | ||||||
|  |      */ | ||||||
|  |     protected async fetchCourses(): Promise<void> { | ||||||
|  |         try { | ||||||
|  |             const courses: CoreEnrolledCourseDataWithExtraInfoAndOptions[] = await CoreCourses.instance.getUserCourses(); | ||||||
|  |             const courseIds = courses.map((course) => course.id); | ||||||
|  | 
 | ||||||
|  |             this.courseIds = courseIds.join(','); | ||||||
|  | 
 | ||||||
|  |             await CoreCoursesHelper.instance.loadCoursesExtraInfo(courses); | ||||||
|  | 
 | ||||||
|  |             if (CoreCourses.instance.canGetAdminAndNavOptions()) { | ||||||
|  |                 const options = await CoreCourses.instance.getCoursesAdminAndNavOptions(courseIds); | ||||||
|  |                 courses.forEach((course) => { | ||||||
|  |                     course.navOptions = options.navOptions[course.id]; | ||||||
|  |                     course.admOptions = options.admOptions[course.id]; | ||||||
|  |                 }); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             this.courses = courses; | ||||||
|  |             this.filteredCourses = this.courses; | ||||||
|  |             this.filter = ''; | ||||||
|  |         } 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()); | ||||||
|  |         // @todo promises.push(this.courseOptionsDelegate.clearAndInvalidateCoursesOptions());
 | ||||||
|  |         if (this.courseIds) { | ||||||
|  |             promises.push(CoreCourses.instance.invalidateCoursesByField('ids', this.courseIds)); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         Promise.all(promises).finally(() => { | ||||||
|  |             this.fetchCourses().finally(() => { | ||||||
|  |                 refresher?.detail.complete(); | ||||||
|  |             }); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Show or hide the filter. | ||||||
|  |      */ | ||||||
|  |     switchFilter(): void { | ||||||
|  |         this.filter = ''; | ||||||
|  |         this.showFilter = !this.showFilter; | ||||||
|  |         this.filteredCourses = this.courses; | ||||||
|  |         if (this.showFilter) { | ||||||
|  |             setTimeout(() => { | ||||||
|  |                 this.searchbar.setFocus(); | ||||||
|  |             }, 500); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * The filter has changed. | ||||||
|  |      * | ||||||
|  |      * @param Received Event. | ||||||
|  |      */ | ||||||
|  |     filterChanged(event?: Event): void { | ||||||
|  |         const target = <HTMLInputElement>event?.target || null; | ||||||
|  |         const newValue = target ? String(target.value).trim().toLowerCase() : null; | ||||||
|  |         if (!newValue || !this.courses) { | ||||||
|  |             this.filteredCourses = this.courses; | ||||||
|  |         } else { | ||||||
|  |             // Use displayname if avalaible, or fullname if not.
 | ||||||
|  |             if (this.courses.length > 0 && typeof this.courses[0].displayname != 'undefined') { | ||||||
|  |                 this.filteredCourses = this.courses.filter((course) => course.displayname!.toLowerCase().indexOf(newValue) > -1); | ||||||
|  |             } else { | ||||||
|  |                 this.filteredCourses = this.courses.filter((course) => course.fullname.toLowerCase().indexOf(newValue) > -1); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Prefetch all the courses. | ||||||
|  |      * | ||||||
|  |      * @return Promise resolved when done. | ||||||
|  |      */ | ||||||
|  |     async prefetchCourses(): Promise<void> { | ||||||
|  |         this.downloadAllCoursesLoading = true; | ||||||
|  | 
 | ||||||
|  |         try { | ||||||
|  |             await CoreCourseHelper.instance.confirmAndPrefetchCourses(this.courses, (progress) => { | ||||||
|  |                 this.downloadAllCoursesBadge = progress.count + ' / ' + progress.total; | ||||||
|  |             }); | ||||||
|  |         } catch (error) { | ||||||
|  |             if (!this.isDestroyed) { | ||||||
|  |                 CoreDomUtils.instance.showErrorModalDefault(error, 'core.course.errordownloadingcourse', true); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         this.downloadAllCoursesBadge = ''; | ||||||
|  |         this.downloadAllCoursesLoading = false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Go to search courses. | ||||||
|  |      */ | ||||||
|  |     openSearch(): void { | ||||||
|  |         this.navCtrl.navigateForward(['/courses/search']); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Page destroyed. | ||||||
|  |      */ | ||||||
|  |     ngOnDestroy(): void { | ||||||
|  |         this.isDestroyed = true; | ||||||
|  |         this.myCoursesObserver?.off(); | ||||||
|  |         this.siteUpdatedObserver?.off(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
| @ -64,6 +64,11 @@ ion-alert.core-alert-network-error .alert-head { | |||||||
|     right: unset; |     right: unset; | ||||||
|     left: -15%; |     left: -15%; | ||||||
| } | } | ||||||
|  | ion-alert.core-nohead { | ||||||
|  |     .alert-head { | ||||||
|  |         padding-bottom: 0; | ||||||
|  |     } | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| // Ionic item divider. | // Ionic item divider. | ||||||
| ion-item-divider { | ion-item-divider { | ||||||
| @ -76,6 +81,16 @@ ion-list.list-md { | |||||||
|     padding-bottom: 0; |     padding-bottom: 0; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // Header. | ||||||
|  | ion-tabs.hide-header ion-header { | ||||||
|  |     display: none; | ||||||
|  | } | ||||||
|  | ion-toolbar { | ||||||
|  |     ion-spinner { | ||||||
|  |         margin: 10px; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // Modals. | // Modals. | ||||||
| .core-modal-fullscreen  .modal-wrapper { | .core-modal-fullscreen  .modal-wrapper { | ||||||
|     position: absolute; |     position: absolute; | ||||||
|  | |||||||
| @ -103,6 +103,10 @@ | |||||||
|     ion-toolbar { |     ion-toolbar { | ||||||
|         --color: var(--custom-toolbar-color, var(--ion-color-primary-contrast)); |         --color: var(--custom-toolbar-color, var(--ion-color-primary-contrast)); | ||||||
|         --background: var(--custom-toolbar-background, var(--ion-color-primary)); |         --background: var(--custom-toolbar-background, var(--ion-color-primary)); | ||||||
|  | 
 | ||||||
|  |         ion-spinner { | ||||||
|  |             --color: var(--custom-toolbar-color, var(--ion-color-primary-contrast)); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     ion-action-sheet { |     ion-action-sheet { | ||||||
| @ -149,6 +153,18 @@ | |||||||
| 
 | 
 | ||||||
|     --core-login-background: var(--custom-login-background, var(--white)); |     --core-login-background: var(--custom-login-background, var(--white)); | ||||||
|     --core-login-text-color: var(--custom-login-text-color, var(--black)); |     --core-login-text-color: var(--custom-login-text-color, var(--black)); | ||||||
|  | 
 | ||||||
|  |     --core-course-image-background-0: var(--custom-course-image-background-0, #81ecec); | ||||||
|  |     --core-course-image-background-1: var(--custom-course-image-background-1, #74b9ff); | ||||||
|  |     --core-course-image-background-2: var(--custom-course-image-background-2, #a29bfe); | ||||||
|  |     --core-course-image-background-3: var(--custom-course-image-background-3, #dfe6e9); | ||||||
|  |     --core-course-image-background-4: var(--custom-course-image-background-4, #00b894); | ||||||
|  |     --core-course-image-background-5: var(--custom-course-image-background-5, #0984e3); | ||||||
|  |     --core-course-image-background-6: var(--custom-course-image-background-6, #b2bec3); | ||||||
|  |     --core-course-image-background-7: var(--custom-course-image-background-7, #fdcb6e); | ||||||
|  |     --core-course-image-background-8: var(--custom-course-image-background-9, #fd79a8); | ||||||
|  |     --core-course-image-background-9: var(--custom-course-image-background-90, #6c5ce7); | ||||||
|  |     --core-star-color: var(--custom-star-color, var(--core-color)); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /* | /* | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user