commit
						26482ea355
					
				| @ -1549,6 +1549,7 @@ | ||||
|   "core.course.downloadcourse": "tool_mobile", | ||||
|   "core.course.downloadcoursesprogressdescription": "local_moodlemobileapp", | ||||
|   "core.course.downloadsectionprogressdescription": "local_moodlemobileapp", | ||||
|   "core.course.enddate": "moodle", | ||||
|   "core.course.errordownloadingcourse": "local_moodlemobileapp", | ||||
|   "core.course.errordownloadingsection": "local_moodlemobileapp", | ||||
|   "core.course.errorgetmodule": "local_moodlemobileapp", | ||||
| @ -1568,6 +1569,7 @@ | ||||
|   "core.course.overriddennotice": "grades", | ||||
|   "core.course.refreshcourse": "local_moodlemobileapp", | ||||
|   "core.course.section": "moodle", | ||||
|   "core.course.startdate": "moodle", | ||||
|   "core.course.thisweek": "format_weeks/currentsection", | ||||
|   "core.course.todo": "completion", | ||||
|   "core.course.useactivityonbrowser": "local_moodlemobileapp", | ||||
|  | ||||
| @ -32,9 +32,9 @@ const routes: Routes = [ | ||||
|         loadChildren: () => import('./pages/list-mod-type/list-mod-type.module').then(m => m.CoreCourseListModTypePageModule), | ||||
|     }, | ||||
|     { | ||||
|         path: ':courseId/preview', | ||||
|         path: ':courseId/summary', | ||||
|         loadChildren: () => | ||||
|             import('./pages/preview/preview.module').then(m => m.CoreCoursePreviewPageModule), | ||||
|             import('./pages/course-summary/course-summary.module').then(m => m.CoreCourseSummaryPageModule), | ||||
|     }, | ||||
| ]; | ||||
| 
 | ||||
|  | ||||
| @ -32,6 +32,7 @@ | ||||
|     "downloadcourse": "Download course", | ||||
|     "downloadcoursesprogressdescription": "Downloading courses: downloaded {{count}} out of {{total}}.", | ||||
|     "downloadsectionprogressdescription": "Downloading section: downloaded {{count}} out of {{total}}.", | ||||
|     "enddate": "Course end date", | ||||
|     "errordownloadingcourse": "Error downloading course.", | ||||
|     "errordownloadingsection": "Error downloading section.", | ||||
|     "errorgetmodule": "Error getting activity data.", | ||||
| @ -51,6 +52,7 @@ | ||||
|     "overriddennotice": "Your final grade from this activity was manually adjusted.", | ||||
|     "refreshcourse": "Refresh course", | ||||
|     "section": "Section", | ||||
|     "startdate": "Course start date", | ||||
|     "thisweek": "This week", | ||||
|     "todo": "To do", | ||||
|     "useactivityonbrowser": "You can still use it using your device's web browser.", | ||||
|  | ||||
| @ -1,9 +1,4 @@ | ||||
| <core-navbar-buttons slot="end"> | ||||
|     <core-context-menu> | ||||
|         <core-context-menu-item *ngFor="let item of courseMenuHandlers" [priority]="item.priority" (action)="openMenuItem(item)" | ||||
|             [content]="item.data.title | translate" [iconAction]="item.data.icon" [class]="item.data.class"> | ||||
|         </core-context-menu-item> | ||||
|     </core-context-menu> | ||||
| </core-navbar-buttons> | ||||
| <ion-content> | ||||
|     <ion-refresher slot="fixed" [disabled]="!dataLoaded || !displayRefresher" (ionRefresh)="doRefresh($event.target)"> | ||||
|  | ||||
| @ -29,10 +29,7 @@ import { | ||||
| } from '@features/course/services/course-helper'; | ||||
| import { CoreCourseFormatDelegate } from '@features/course/services/format-delegate'; | ||||
| import { CoreCourseModulePrefetchDelegate } from '@features/course/services/module-prefetch-delegate'; | ||||
| import { | ||||
|     CoreCourseOptionsDelegate, | ||||
|     CoreCourseOptionsMenuHandlerToDisplay, | ||||
| } from '@features/course/services/course-options-delegate'; | ||||
| import { CoreCourseOptionsMenuHandlerToDisplay } from '@features/course/services/course-options-delegate'; | ||||
| import { CoreCourseSync, CoreCourseSyncProvider } from '@features/course/services/sync'; | ||||
| import { CoreCourseFormatComponent } from '../../components/format/format'; | ||||
| import { | ||||
| @ -69,7 +66,6 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy { | ||||
|     protected syncObserver?: CoreEventObserver; | ||||
|     protected isDestroyed = false; | ||||
|     protected modulesHaveCompletion = false; | ||||
|     protected isGuest = false; | ||||
|     protected debouncedUpdateCachedCompletion?: () => void; // Update the cached completion after a certain time.
 | ||||
| 
 | ||||
|     /** | ||||
| @ -89,7 +85,6 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy { | ||||
|         this.sectionId = CoreNavigator.getRouteNumberParam('sectionId'); | ||||
|         this.sectionNumber = CoreNavigator.getRouteNumberParam('sectionNumber'); | ||||
|         this.moduleId = CoreNavigator.getRouteNumberParam('moduleId'); | ||||
|         this.isGuest = !!CoreNavigator.getRouteBooleanParam('isGuest'); | ||||
| 
 | ||||
|         this.debouncedUpdateCachedCompletion = CoreUtils.debounce(() => { | ||||
|             if (this.modulesHaveCompletion) { | ||||
| @ -184,7 +179,6 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy { | ||||
|         try { | ||||
|             await Promise.all([ | ||||
|                 this.loadSections(refresh), | ||||
|                 this.loadMenuHandlers(refresh), | ||||
|                 this.loadCourseFormatOptions(), | ||||
|             ]); | ||||
|         } catch (error) { | ||||
| @ -248,16 +242,6 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy { | ||||
|         this.displayRefresher = CoreCourseFormatDelegate.displayRefresher(this.course, this.sections); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Load the course menu handlers. | ||||
|      * | ||||
|      * @param refresh If it's refreshing content. | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     protected async loadMenuHandlers(refresh?: boolean): Promise<void> { | ||||
|         this.courseMenuHandlers = await CoreCourseOptionsDelegate.getMenuHandlersToDisplay(this.course, refresh, this.isGuest); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Load course format options if needed. | ||||
|      * | ||||
| @ -379,16 +363,6 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy { | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Opens a menu item registered to the delegate. | ||||
|      * | ||||
|      * @param item Item to open | ||||
|      */ | ||||
|     openMenuItem(item: CoreCourseOptionsMenuHandlerToDisplay): void { | ||||
|         const params = Object.assign({ course: this.course }, item.data.pageParams); | ||||
|         CoreNavigator.navigateToSitePath(item.data.page, { params }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Page destroyed. | ||||
|      */ | ||||
|  | ||||
| @ -8,6 +8,11 @@ | ||||
|                 {{'core.course.coursesummary' | translate}} | ||||
|             </h1> | ||||
|         </ion-title> | ||||
|         <ion-buttons slot="end" *ngIf="isModal"> | ||||
|             <ion-button fill="clear" (click)="closeModal()" [attr.aria-label]="'core.close' | translate"> | ||||
|                 <ion-icon slot="icon-only" name="fas-times" aria-hidden="true"></ion-icon> | ||||
|             </ion-button> | ||||
|         </ion-buttons> | ||||
|     </ion-toolbar> | ||||
| </ion-header> | ||||
| <ion-content> | ||||
| @ -15,12 +20,10 @@ | ||||
|         <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content> | ||||
|     </ion-refresher> | ||||
|     <core-loading [hideUntil]="dataLoaded"> | ||||
|         <div *ngIf="courseImageUrl" class="core-course-thumb-parallax"> | ||||
|             <div class="core-course-thumb"> | ||||
|                 <img [src]="courseImageUrl" core-external-content alt="" /> | ||||
|             </div> | ||||
|         <div *ngIf="courseImageUrl" class="core-course-thumb"> | ||||
|             <img [src]="courseImageUrl" core-external-content alt="" /> | ||||
|         </div> | ||||
|         <div class="core-course-thumb-parallax-content" *ngIf="course"> | ||||
|         <ng-container *ngIf="course"> | ||||
|             <ion-item class="ion-text-wrap"> | ||||
|                 <ion-label> | ||||
|                     <p *ngIf="course.categoryname"> | ||||
| @ -31,15 +34,28 @@ | ||||
|                         <core-format-text [text]="course.fullname" contextLevel="course" [contextInstanceId]="course.id"> | ||||
|                         </core-format-text> | ||||
|                     </h2> | ||||
|                     <p *ngIf="course.startdate"> | ||||
|                         {{course.startdate * 1000 | coreFormatDate:"strftimedatefullshort" }} | ||||
|                         <span *ngIf="course.enddate"> - {{course.enddate * 1000 | coreFormatDate:"strftimedatefullshort" }}</span> | ||||
|                     </p> | ||||
| 
 | ||||
|                     <div class="core-course-progress" *ngIf="progress !== undefined"> | ||||
|                         <core-progress-bar [progress]="progress" a11yText="core.course.aria:sectionprogress"> | ||||
|                         </core-progress-bar> | ||||
|                     </div> | ||||
|                     <div *ngIf="course.startdate || course.enddate" class="core-course-dates"> | ||||
|                         <p *ngIf="course.startdate"> | ||||
|                             <ion-icon name="fas-calendar" aria-hidden="true"></ion-icon> | ||||
|                             <strong>{{ 'core.course.startdate' | translate }}: </strong> {{ course.startdate * 1000 | | ||||
|                             coreFormatDate:'strftimedatefullshort' }} | ||||
|                         </p> | ||||
|                         <p *ngIf="course.enddate"> | ||||
|                             <ion-icon name="fas-calendar" aria-hidden="true"></ion-icon> | ||||
|                             <strong>{{ 'core.course.enddate' | translate }}: </strong> {{ course.enddate * 1000 | | ||||
|                             coreFormatDate:'strftimedatefullshort' }} | ||||
|                         </p> | ||||
|                     </div> | ||||
|                 </ion-label> | ||||
|                 <ion-button fill="clear" [href]="courseUrl" core-link [showBrowserWarning]="false" color="dark" | ||||
|                     [attr.aria-label]="'core.openinbrowser' | translate" slot="end"> | ||||
|                     <ion-icon name="fas-external-link-alt" slot="icon-only" aria-hidden="true"></ion-icon> | ||||
|                 </ion-button> | ||||
|             </ion-item> | ||||
| 
 | ||||
|             <ion-item class="ion-text-wrap" *ngIf="course.summary" detail="false"> | ||||
| @ -55,7 +71,7 @@ | ||||
|             <ion-list *ngIf="course.contacts && course.contacts.length"> | ||||
|                 <ion-item-divider class="ion-text-wrap"> | ||||
|                     <ion-label> | ||||
|                         <h2>{{ 'core.teachers' | translate }}</h2> | ||||
|                         <p class="item-heading">{{ 'core.teachers' | translate }}</p> | ||||
|                     </ion-label> | ||||
|                 </ion-item-divider> | ||||
|                 <ion-item button class="ion-text-wrap" *ngFor="let contact of course.contacts" core-user-link [userId]="contact.id" | ||||
| @ -87,59 +103,48 @@ | ||||
|                     </ng-container> | ||||
|                 </ion-label> | ||||
|             </ion-item> | ||||
| 
 | ||||
|             <!-- Enrol --> | ||||
|             <ng-container *ngIf="!isEnrolled"> | ||||
|                 <ion-item class="ion-text-wrap" *ngFor="let instance of selfEnrolInstances"> | ||||
|                     <ion-label> | ||||
|                         <p class="item-heading">{{ instance.name }}</p> | ||||
|                         <ion-button expand="block" class="ion-margin-top" (click)="selfEnrolClicked(instance.id)"> | ||||
|                             {{ 'core.courses.enrolme' | translate }} | ||||
|                         </ion-button> | ||||
|                     </ion-label> | ||||
|                 </ion-item> | ||||
|                 <ion-item class="ion-text-wrap" *ngIf="paypalEnabled"> | ||||
|                     <ion-label> | ||||
|                         <p class="item-heading">{{ 'core.courses.otherenrolments' | translate }}</p> | ||||
|                         <ion-button expand="block" class="ion-margin-top" (click)="browserEnrol()"> | ||||
|                             {{ 'core.courses.completeenrolmentbrowser' | translate }} | ||||
|                         </ion-button> | ||||
|                     </ion-label> | ||||
|                 </ion-item> | ||||
|                 <ion-item *ngIf="!selfEnrolInstances.length && !paypalEnabled"> | ||||
|                     <ion-label> | ||||
|                         <p class="item-heading">{{ 'core.courses.notenrollable' | translate }}</p> | ||||
|                     </ion-label> | ||||
|                 </ion-item> | ||||
|             </ng-container> | ||||
| 
 | ||||
|             <ion-button class="ion-margin" *ngIf="canAccessCourse && downloadCourseEnabled" (click)="prefetchCourse()" expand="block" | ||||
|                 [attr.aria-label]="prefetchCourseData.statusTranslatable | translate"> | ||||
|                 <ion-icon *ngIf="(prefetchCourseData.status != statusDownloaded) && !prefetchCourseData.loading" | ||||
|                     [name]="prefetchCourseData.icon" slot="start" aria-hidden="true"> | ||||
|                 </ion-icon> | ||||
|                 <ion-icon *ngIf="(prefetchCourseData.status == statusDownloaded) && !prefetchCourseData.loading" slot="start" | ||||
|                     [name]="prefetchCourseData.icon" color="success" aria-hidden="true" role="status"> | ||||
|                 </ion-icon> | ||||
|                 <ion-spinner *ngIf="prefetchCourseData.loading" slot="start" [attr.aria-label]="'core.loading' | translate"></ion-spinner> | ||||
|                 <ion-label *ngIf="prefetchCourseData.status != statusDownloaded">{{ 'core.course.downloadcourse' | translate }}</ion-label> | ||||
|                 <ion-label *ngIf="prefetchCourseData.status == statusDownloaded">{{ 'core.course.refreshcourse' | translate }}</ion-label> | ||||
|             </ion-button> | ||||
| 
 | ||||
|             <ion-button class="ion-margin" (click)="openCourse()" *ngIf="!avoidOpenCourse && canAccessCourse" expand="block"> | ||||
|                 <ion-icon name="fas-briefcase" slot="start" aria-hidden="true"></ion-icon> | ||||
|                 <ion-label> | ||||
|                     {{ 'core.course' | translate }} | ||||
|                 </ion-label> | ||||
|             </ion-button> | ||||
| 
 | ||||
|             <ion-button class="ion-margin" [href]="courseUrl" core-link [showBrowserWarning]="false" expand="block"> | ||||
|                 <ion-icon name="fas-external-link-alt" slot="start" aria-hidden="true"></ion-icon> | ||||
|                 <ion-label> | ||||
|                     {{ 'core.openinbrowser' | translate }} | ||||
|                 </ion-label> | ||||
|             </ion-button> | ||||
| 
 | ||||
|         </div> | ||||
|         </ng-container> | ||||
|     </core-loading> | ||||
| </ion-content> | ||||
| 
 | ||||
| <ion-footer *ngIf="course && dataLoaded"> | ||||
|     <!-- Enrol --> | ||||
|     <ion-card *ngIf="!isEnrolled"> | ||||
|         <ion-item class="ion-text-wrap" *ngFor="let instance of selfEnrolInstances"> | ||||
|             <ion-label> | ||||
|                 <p class="item-heading">{{ instance.name }}</p> | ||||
|                 <ion-button expand="block" class="ion-margin-top" (click)="selfEnrolClicked(instance.id)"> | ||||
|                     {{ 'core.courses.enrolme' | translate }} | ||||
|                 </ion-button> | ||||
|             </ion-label> | ||||
|         </ion-item> | ||||
|         <ion-item class="ion-text-wrap" *ngIf="paypalEnabled"> | ||||
|             <ion-label> | ||||
|                 <p class="item-heading">{{ 'core.courses.otherenrolments' | translate }}</p> | ||||
|                 <ion-button expand="block" class="ion-margin-top" (click)="browserEnrol()"> | ||||
|                     {{ 'core.courses.completeenrolmentbrowser' | translate }} | ||||
|                 </ion-button> | ||||
|             </ion-label> | ||||
|         </ion-item> | ||||
|         <ion-item *ngIf="!selfEnrolInstances.length && !paypalEnabled"> | ||||
|             <ion-label> | ||||
|                 <p class="item-heading">{{ 'core.courses.notenrollable' | translate }}</p> | ||||
|             </ion-label> | ||||
|         </ion-item> | ||||
|     </ion-card> | ||||
| 
 | ||||
|     <ng-container *ngIf="canAccessCourse"> | ||||
|         <ion-button class="ion-margin" *ngFor="let item of courseMenuHandlers" (click)="openMenuItem(item)" [class]="item.data.class" | ||||
|             expand="block"> | ||||
|             <ion-icon *ngIf="item.data.icon" [name]="item.data.icon" slot="start" aria-hidden="true"></ion-icon> | ||||
|             <ion-label>{{item.data.title | translate }}</ion-label> | ||||
|         </ion-button> | ||||
|     </ng-container> | ||||
| 
 | ||||
|     <ion-button class="ion-margin" (click)="openCourse()" *ngIf="!isModal && canAccessCourse" expand="block"> | ||||
|         <ion-icon name="fas-briefcase" slot="start" aria-hidden="true"></ion-icon> | ||||
|         <ion-label> | ||||
|             {{ 'core.course' | translate }} | ||||
|         </ion-label> | ||||
|     </ion-button> | ||||
| </ion-footer> | ||||
| @ -16,23 +16,30 @@ import { NgModule } from '@angular/core'; | ||||
| import { RouterModule, Routes } from '@angular/router'; | ||||
| 
 | ||||
| import { CoreSharedModule } from '@/core/shared.module'; | ||||
| import { CoreCoursePreviewPage } from './preview.page'; | ||||
| import { CoreCourseSummaryPage } from './course-summary'; | ||||
| 
 | ||||
| const routes: Routes = [ | ||||
|     { | ||||
|         path: '', | ||||
|         component: CoreCoursePreviewPage, | ||||
|         component: CoreCourseSummaryPage, | ||||
|     }, | ||||
| ]; | ||||
| @NgModule({ | ||||
|     imports: [ | ||||
|         CoreSharedModule, | ||||
|     ], | ||||
|     declarations: [ | ||||
|         CoreCourseSummaryPage, | ||||
|     ], | ||||
| }) | ||||
| export class CoreCoursePreviewPageComponentModule { } | ||||
| 
 | ||||
| @NgModule({ | ||||
|     imports: [ | ||||
|         RouterModule.forChild(routes), | ||||
|         CoreSharedModule, | ||||
|     ], | ||||
|     declarations: [ | ||||
|         CoreCoursePreviewPage, | ||||
|         CoreCoursePreviewPageComponentModule, | ||||
|     ], | ||||
|     exports: [RouterModule], | ||||
| }) | ||||
| export class CoreCoursePreviewPageModule { } | ||||
| export class CoreCourseSummaryPageModule { } | ||||
| @ -0,0 +1,28 @@ | ||||
| @import '~theme/globals.scss'; | ||||
| 
 | ||||
| :host { | ||||
|     .core-course-thumb { | ||||
|         overflow: hidden; | ||||
|         text-align: center; | ||||
|         max-height: 35vh; | ||||
|         z-index: -1; | ||||
|         overflow: hidden; | ||||
|         border-bottom: 1px solid var(--stroke); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     .core-customfieldvalue core-format-text { | ||||
|         display: inline; | ||||
|     } | ||||
| 
 | ||||
|     .core-course-dates { | ||||
|         background: var(--light); | ||||
|         border-radius: var(--small-radius); | ||||
|         padding: 8px; | ||||
| 
 | ||||
|         ion-icon { | ||||
|             @include margin-horizontal(null, 8px); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -12,7 +12,7 @@ | ||||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| import { Component, OnDestroy, OnInit } from '@angular/core'; | ||||
| import { Component, OnDestroy, OnInit, Input } from '@angular/core'; | ||||
| import { IonRefresher } from '@ionic/angular'; | ||||
| import { CoreEventObserver, CoreEvents } from '@singletons/events'; | ||||
| import { CoreSites } from '@services/sites'; | ||||
| @ -26,11 +26,12 @@ import { | ||||
|     CoreCoursesProvider, | ||||
|     CoreEnrolledCourseData, | ||||
| } from '@features/courses/services/courses'; | ||||
| import { CoreCourseOptionsDelegate } from '@features/course/services/course-options-delegate'; | ||||
| import { CoreCourse, CoreCourseProvider } from '@features/course/services/course'; | ||||
| import { CoreCourseHelper, CorePrefetchStatusInfo } from '@features/course/services/course-helper'; | ||||
| import { NgZone, Platform, Translate } from '@singletons'; | ||||
| import { CoreConstants } from '@/core/constants'; | ||||
| import { | ||||
|     CoreCourseOptionsDelegate, | ||||
|     CoreCourseOptionsMenuHandlerToDisplay, | ||||
| } from '@features/course/services/course-options-delegate'; | ||||
| import { CoreCourseHelper } from '@features/course/services/course-helper'; | ||||
| import { ModalController, NgZone, Platform, Translate } from '@singletons'; | ||||
| import { CoreCoursesSelfEnrolPasswordComponent } from '../../../courses/components/self-enrol-password/self-enrol-password'; | ||||
| import { CoreNavigator } from '@services/navigator'; | ||||
| import { CoreUtils } from '@services/utils/utils'; | ||||
| @ -38,36 +39,31 @@ import { CoreCourseWithImageAndColor } from '@features/courses/services/courses- | ||||
| import { Subscription } from 'rxjs'; | ||||
| 
 | ||||
| /** | ||||
|  * Page that allows "previewing" a course and enrolling in it if enabled and not enrolled. | ||||
|  * Page that shows the summary of a course including buttons to enrol and other available options. | ||||
|  */ | ||||
| @Component({ | ||||
|     selector: 'page-core-course-preview', | ||||
|     templateUrl: 'preview.html', | ||||
|     styleUrls: ['preview.scss'], | ||||
|     selector: 'page-core-course-summary', | ||||
|     templateUrl: 'course-summary.html', | ||||
|     styleUrls: ['course-summary.scss'], | ||||
| }) | ||||
| export class CoreCoursePreviewPage implements OnInit, OnDestroy { | ||||
| export class CoreCourseSummaryPage implements OnInit, OnDestroy { | ||||
| 
 | ||||
|     @Input() course?: CoreCourseSummaryData; | ||||
|     @Input() courseId = 0; | ||||
| 
 | ||||
|     course?: CoreCourseSummaryData; | ||||
|     isEnrolled = false; | ||||
|     canAccessCourse = true; | ||||
|     selfEnrolInstances: CoreCourseEnrolmentMethod[] = []; | ||||
|     paypalEnabled = false; | ||||
|     dataLoaded = false; | ||||
|     avoidOpenCourse = false; | ||||
|     prefetchCourseData: CorePrefetchStatusInfo = { | ||||
|         icon: '', | ||||
|         statusTranslatable: 'core.loading', | ||||
|         status: '', | ||||
|         loading: true, | ||||
|     }; | ||||
|     isModal = false; | ||||
| 
 | ||||
|     statusDownloaded = CoreConstants.DOWNLOADED; | ||||
| 
 | ||||
|     downloadCourseEnabled: boolean; | ||||
|     courseUrl = ''; | ||||
|     courseImageUrl?: string; | ||||
|     progress?: number; | ||||
| 
 | ||||
|     courseMenuHandlers: CoreCourseOptionsMenuHandlerToDisplay[] = []; | ||||
| 
 | ||||
|     protected isGuestEnabled = false; | ||||
|     protected useGuestAccess = false; | ||||
|     protected guestInstanceId?: number; | ||||
| @ -76,22 +72,10 @@ export class CoreCoursePreviewPage implements OnInit, OnDestroy { | ||||
|     protected enrolUrl = ''; | ||||
|     protected pageDestroyed = false; | ||||
|     protected courseStatusObserver?: CoreEventObserver; | ||||
|     protected courseId!: number; | ||||
|     protected appResumeSubscription: Subscription; | ||||
|     protected waitingForBrowserEnrol = false; | ||||
| 
 | ||||
|     constructor() { | ||||
|         this.downloadCourseEnabled = !CoreCourses.isDownloadCourseDisabledInSite(); | ||||
| 
 | ||||
|         if (this.downloadCourseEnabled) { | ||||
|             // Listen for status change in course.
 | ||||
|             this.courseStatusObserver = CoreEvents.on(CoreEvents.COURSE_STATUS_CHANGED, (data) => { | ||||
|                 if (data.courseId == this.courseId || data.courseId == CoreCourseProvider.ALL_COURSES_CLEARED) { | ||||
|                     this.updateCourseStatus(data.status); | ||||
|                 } | ||||
|             }, CoreSites.getCurrentSiteId()); | ||||
|         } | ||||
| 
 | ||||
|         // Refresh the view when the app is resumed.
 | ||||
|         this.appResumeSubscription = Platform.resume.subscribe(() => { | ||||
|             if (!this.waitingForBrowserEnrol || !this.dataLoaded) { | ||||
| @ -111,47 +95,29 @@ export class CoreCoursePreviewPage implements OnInit, OnDestroy { | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     async ngOnInit(): Promise<void> { | ||||
|         try { | ||||
|             this.courseId = CoreNavigator.getRequiredRouteNumberParam('courseId'); | ||||
|         } catch (error) { | ||||
|             CoreDomUtils.showErrorModal(error); | ||||
|             CoreNavigator.back(); | ||||
|         if (!this.courseId) { | ||||
|             // Opened as a page.
 | ||||
|             try { | ||||
|                 this.courseId = CoreNavigator.getRequiredRouteNumberParam('courseId'); | ||||
|             } catch (error) { | ||||
|                 CoreDomUtils.showErrorModal(error); | ||||
|                 CoreNavigator.back(); | ||||
|                 this.closeModal(); // Just in case.
 | ||||
| 
 | ||||
|             return; | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             this.course = CoreNavigator.getRouteParam('course'); | ||||
|         } else { | ||||
|             // Opened as a modal.
 | ||||
|             this.isModal = true; | ||||
|         } | ||||
| 
 | ||||
|         this.avoidOpenCourse = !!CoreNavigator.getRouteBooleanParam('avoidOpenCourse'); | ||||
|         this.course = CoreNavigator.getRouteParam('course'); | ||||
| 
 | ||||
|         const currentSiteUrl = CoreSites.getRequiredCurrentSite().getURL(); | ||||
|         this.enrolUrl = CoreTextUtils.concatenatePaths(currentSiteUrl, 'enrol/index.php?id=' + this.courseId); | ||||
|         this.courseUrl = CoreTextUtils.concatenatePaths(currentSiteUrl, 'course/view.php?id=' + this.courseId); | ||||
| 
 | ||||
|         try { | ||||
|             await this.getCourse(); | ||||
|         } finally { | ||||
|             if (this.downloadCourseEnabled) { | ||||
| 
 | ||||
|                 // Determine course prefetch icon.
 | ||||
|                 this.prefetchCourseData = await CoreCourseHelper.getCourseStatusIconAndTitle(this.courseId); | ||||
| 
 | ||||
|                 if (this.prefetchCourseData.loading) { | ||||
|                     // Course is being downloaded. Get the download promise.
 | ||||
|                     const promise = CoreCourseHelper.getCourseDownloadPromise(this.courseId); | ||||
|                     if (promise) { | ||||
|                         // There is a download promise. If it fails, show an error.
 | ||||
|                         promise.catch((error) => { | ||||
|                             if (!this.pageDestroyed) { | ||||
|                                 CoreDomUtils.showErrorModalDefault(error, 'core.course.errordownloadingcourse', true); | ||||
|                             } | ||||
|                         }); | ||||
|                     } else { | ||||
|                         // No download, this probably means that the app was closed while downloading. Set previous status.
 | ||||
|                         CoreCourse.setCoursePreviousStatus(this.courseId); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         await this.getCourse(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -184,8 +150,10 @@ export class CoreCoursePreviewPage implements OnInit, OnDestroy { | ||||
| 
 | ||||
|     /** | ||||
|      * Convenience function to get course. We use this to determine if a user can see the course or not. | ||||
|      * | ||||
|      * @param refresh If it's refreshing content. | ||||
|      */ | ||||
|     protected async getCourse(): Promise<void> { | ||||
|     protected async getCourse(refresh = false): Promise<void> { | ||||
|         // Get course enrolment methods.
 | ||||
|         this.selfEnrolInstances = []; | ||||
| 
 | ||||
| @ -262,16 +230,33 @@ export class CoreCoursePreviewPage implements OnInit, OnDestroy { | ||||
|             this.progress = this.course.progress; | ||||
|         } | ||||
| 
 | ||||
|         await this.loadMenuHandlers(refresh); | ||||
| 
 | ||||
|         this.dataLoaded = true; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Load the course menu handlers. | ||||
|      * | ||||
|      * @param refresh If it's refreshing content. | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     protected async loadMenuHandlers(refresh?: boolean): Promise<void> { | ||||
|         if (!this.course) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         this.courseMenuHandlers = | ||||
|             await CoreCourseOptionsDelegate.getMenuHandlersToDisplay(this.course, refresh, this.useGuestAccess); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Open the course. | ||||
|      * | ||||
|      * @param replaceCurrentPage If current place should be replaced in the navigation stack. | ||||
|      */ | ||||
|     openCourse(replaceCurrentPage = false): void { | ||||
|         if (!this.canAccessCourse || !this.course || this.avoidOpenCourse) { | ||||
|         if (!this.canAccessCourse || !this.course || this.isModal) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
| @ -403,20 +388,6 @@ export class CoreCoursePreviewPage implements OnInit, OnDestroy { | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Update the course status icon and title. | ||||
|      * | ||||
|      * @param status Status to show. | ||||
|      */ | ||||
|     protected updateCourseStatus(status: string): void { | ||||
|         const statusData = CoreCourseHelper.getCoursePrefetchStatusInfo(status); | ||||
| 
 | ||||
|         this.prefetchCourseData.status = statusData.status; | ||||
|         this.prefetchCourseData.icon = statusData.icon; | ||||
|         this.prefetchCourseData.statusTranslatable = statusData.statusTranslatable; | ||||
|         this.prefetchCourseData.loading = statusData.loading; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Wait for the user to be enrolled in the course. | ||||
|      * | ||||
| @ -453,18 +424,20 @@ export class CoreCoursePreviewPage implements OnInit, OnDestroy { | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Prefetch the course. | ||||
|      * Opens a menu item registered to the delegate. | ||||
|      * | ||||
|      * @param item Item to open | ||||
|      */ | ||||
|     async prefetchCourse(): Promise<void> { | ||||
|         try { | ||||
|             await CoreCourseHelper.confirmAndPrefetchCourse(this.prefetchCourseData, this.course as CoreEnrolledCourseData, { | ||||
|                 isGuest: this.useGuestAccess, | ||||
|             }); | ||||
|         } catch (error) { | ||||
|             if (!this.pageDestroyed) { | ||||
|                 CoreDomUtils.showErrorModalDefault(error, 'core.course.errordownloadingcourse', true); | ||||
|             } | ||||
|         } | ||||
|     openMenuItem(item: CoreCourseOptionsMenuHandlerToDisplay): void { | ||||
|         const params = Object.assign({ course: this.course }, item.data.pageParams); | ||||
|         CoreNavigator.navigateToSitePath(item.data.page, { params }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Close the modal. | ||||
|      */ | ||||
|     closeModal(): void { | ||||
|         ModalController.dismiss(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -19,6 +19,7 @@ import { resolveModuleRoutes } from '@/app/app-routing.module'; | ||||
| import { CoreSharedModule } from '@/core/shared.module'; | ||||
| import { CoreCourseIndexPage } from '.'; | ||||
| import { COURSE_INDEX_ROUTES } from './index-routing.module'; | ||||
| import { CoreCoursePreviewPageComponentModule } from '../course-summary/course-summary.module'; | ||||
| 
 | ||||
| function buildRoutes(injector: Injector): Routes { | ||||
|     const routes = resolveModuleRoutes(injector, COURSE_INDEX_ROUTES); | ||||
| @ -42,6 +43,7 @@ function buildRoutes(injector: Injector): Routes { | ||||
|     ], | ||||
|     imports: [ | ||||
|         CoreSharedModule, | ||||
|         CoreCoursePreviewPageComponentModule, | ||||
|     ], | ||||
|     declarations: [ | ||||
|         CoreCourseIndexPage, | ||||
|  | ||||
| @ -28,6 +28,7 @@ import { CoreNavigator } from '@services/navigator'; | ||||
| import { CONTENTS_PAGE_NAME } from '@features/course/course.module'; | ||||
| import { CoreDomUtils } from '@services/utils/dom'; | ||||
| import { CoreCollapsibleHeaderDirective } from '@directives/collapsible-header'; | ||||
| import { CoreCourseSummaryPage } from '../course-summary/course-summary'; | ||||
| 
 | ||||
| /** | ||||
|  * Page that displays the list of courses the user is enrolled in. | ||||
| @ -279,10 +280,13 @@ export class CoreCourseIndexPage implements OnInit, OnDestroy { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         CoreNavigator.navigateToSitePath( | ||||
|             `/course/${this.course.id}/preview`, | ||||
|             { params: { course: this.course, avoidOpenCourse: true } }, | ||||
|         ); | ||||
|         CoreDomUtils.openSideModal<void>({ | ||||
|             component: CoreCourseSummaryPage, | ||||
|             componentProps: { | ||||
|                 courseId: this.course.id, | ||||
|                 course: this.course, | ||||
|             }, | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | ||||
| @ -1,47 +0,0 @@ | ||||
| :host { | ||||
|     ion-content:not(.animating) { | ||||
|         &::part(scroll) { | ||||
|             perspective: 1px; | ||||
|             perspective-origin: center top; | ||||
|             transform-style: preserve-3d; | ||||
|         } | ||||
| 
 | ||||
|         .core-course-thumb { | ||||
|             transform-origin: center top; | ||||
| 
 | ||||
|             --scroll-factor: 0.5; | ||||
|             --translate-z: calc(-2 * var(--scroll-factor))px; | ||||
|             --scale: calc(1 + var(--scroll-factor) * 2); | ||||
| 
 | ||||
|             /** | ||||
|             * Calculated with scroll-factor: 0.5; | ||||
|             * translate-z: -2 * $scroll-factor px; | ||||
|             * scale: 1 + $scroll-factor * 2; | ||||
|             */ | ||||
|             transform: translateZ(-1px) scale(2); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     .core-course-thumb-parallax-content { | ||||
|         transform: translateZ(0); | ||||
|         -webkit-filter: drop-shadow(0px -3px 3px rgba(var(--drop-shadow))); | ||||
|         filter: drop-shadow(0px -3px 3px rgba(var(--drop-shadow))); | ||||
|     } | ||||
| 
 | ||||
|     .core-course-thumb-parallax { | ||||
|         height: 40vw; | ||||
|         max-height: 35vh; | ||||
|         z-index: -1; | ||||
|         overflow: hidden; | ||||
|     } | ||||
| 
 | ||||
|     .core-course-thumb { | ||||
|         overflow: hidden; | ||||
|         text-align: center; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     .core-customfieldvalue core-format-text { | ||||
|         display: inline; | ||||
|     } | ||||
| } | ||||
| @ -171,7 +171,7 @@ export class CoreCoursesCourseListItemComponent implements OnInit, OnDestroy, On | ||||
|             CoreCourseHelper.openCourse(this.course); | ||||
|         } else { | ||||
|             CoreNavigator.navigateToSitePath( | ||||
|                 `/course/${this.course.id}/preview`, | ||||
|                 `/course/${this.course.id}/summary`, | ||||
|                 { params: { course: this.course } }, | ||||
|             ); | ||||
|         } | ||||
|  | ||||
| @ -66,7 +66,7 @@ export class CoreCoursesEnrolPushClickHandlerService implements CorePushNotifica | ||||
|                 params.selectedTab = 'participants'; // @todo: Set this when participants is done.
 | ||||
|             } else if (!result.enrolled) { | ||||
|                 // User not enrolled anymore, open the preview page.
 | ||||
|                 page += '/preview'; | ||||
|                 page += '/summary'; | ||||
|             } | ||||
| 
 | ||||
|             await CoreNavigator.navigateToSitePath(page, { params, siteId: notification.site }); | ||||
|  | ||||
| @ -79,7 +79,7 @@ export class CoreCoursesRequestPushClickHandlerService implements CorePushNotifi | ||||
| 
 | ||||
|             if (!result.enrolled) { | ||||
|                 // User not enrolled (shouldn't happen), open the preview page.
 | ||||
|                 page += '/preview'; | ||||
|                 page += '/summary'; | ||||
|             } | ||||
| 
 | ||||
|             await CoreNavigator.navigateToSitePath(page, { params, siteId: notification.site }); | ||||
|  | ||||
| @ -58,7 +58,6 @@ export class CoreMainMenuUserButtonComponent implements OnInit { | ||||
| 
 | ||||
|         CoreDomUtils.openSideModal<void>({ | ||||
|             component: CoreMainMenuUserMenuComponent, | ||||
|             cssClass: 'core-modal-lateral', | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -18,6 +18,7 @@ information provided here is intended especially for developers. | ||||
| - CoreCourseHelperProvider.openCourse parameters changed, now it admits CoreNavigationOptions + siteId on the same object that includes Params passed to page. | ||||
| - displaySectionSelector has been deprecated on CoreCourseFormatHandler, use displayCourseIndex instead. | ||||
| - Most of the functions or callbacks that handle redirects/deeplinks have been modified to accept an object instead of just path + options. E.g.: CoreLoginHelper.isSiteLoggedOut, CoreLoginHelper.openBrowserForSSOLogin, CoreLoginHelper.openBrowserForOAuthLogin, CoreLoginHelper.prepareForSSOLogin, CoreApp.storeRedirect, CoreSites.loadSite. | ||||
| - Course preview page route has changed from course/:courseId/preview to course/:courseId/summary to match with the page name and characteristics. | ||||
| 
 | ||||
| === 3.9.5 === | ||||
| 
 | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user