MOBILE-3915 course: Improve course summary page
parent
901a445408
commit
6b46f48b3c
|
@ -2257,6 +2257,7 @@
|
||||||
"core.strftimetime24": "langconfig",
|
"core.strftimetime24": "langconfig",
|
||||||
"core.submit": "moodle",
|
"core.submit": "moodle",
|
||||||
"core.success": "moodle",
|
"core.success": "moodle",
|
||||||
|
"core.summary": "moodle",
|
||||||
"core.tablet": "local_moodlemobileapp",
|
"core.tablet": "local_moodlemobileapp",
|
||||||
"core.tag.defautltagcoll": "tag",
|
"core.tag.defautltagcoll": "tag",
|
||||||
"core.tag.errorareanotsupported": "local_moodlemobileapp",
|
"core.tag.errorareanotsupported": "local_moodlemobileapp",
|
||||||
|
|
|
@ -93,6 +93,7 @@ export const moodleTransitionAnimation = (navEl: HTMLElement, opts: TransitionOp
|
||||||
}
|
}
|
||||||
|
|
||||||
rootAnimation.addAnimation(enteringContentAnimation);
|
rootAnimation.addAnimation(enteringContentAnimation);
|
||||||
|
enteringContentAnimation.beforeAddClass('animating').afterRemoveClass('animating');
|
||||||
|
|
||||||
if (backDirection) {
|
if (backDirection) {
|
||||||
enteringContentAnimation
|
enteringContentAnimation
|
||||||
|
@ -214,6 +215,8 @@ export const moodleTransitionAnimation = (navEl: HTMLElement, opts: TransitionOp
|
||||||
// setup leaving view
|
// setup leaving view
|
||||||
if (leavingEl) {
|
if (leavingEl) {
|
||||||
const leavingContent = createAnimation();
|
const leavingContent = createAnimation();
|
||||||
|
leavingContent.beforeAddClass('animating').afterRemoveClass('animating');
|
||||||
|
|
||||||
const leavingContentEl = leavingEl.querySelector(':scope > ion-content');
|
const leavingContentEl = leavingEl.querySelector(':scope > ion-content');
|
||||||
const leavingToolBarEls = leavingEl.querySelectorAll(':scope > ion-header > ion-toolbar');
|
const leavingToolBarEls = leavingEl.querySelectorAll(':scope > ion-header > ion-toolbar');
|
||||||
const leavingHeaderEls = leavingEl.querySelectorAll(':scope > ion-header > *:not(ion-toolbar), :scope > ion-footer > *');
|
const leavingHeaderEls = leavingEl.querySelectorAll(':scope > ion-header > *:not(ion-toolbar), :scope > ion-footer > *');
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
:host ::ng-deep .collapsible-title {
|
||||||
|
display: none;
|
||||||
|
}
|
|
@ -31,6 +31,7 @@ import { CoreCourse } from '@features/course/services/course';
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'core-course-format-single-activity',
|
selector: 'core-course-format-single-activity',
|
||||||
templateUrl: 'core-course-format-single-activity.html',
|
templateUrl: 'core-course-format-single-activity.html',
|
||||||
|
styleUrls: ['single-activity.scss'],
|
||||||
})
|
})
|
||||||
export class CoreCourseFormatSingleActivityComponent implements OnChanges {
|
export class CoreCourseFormatSingleActivityComponent implements OnChanges {
|
||||||
|
|
||||||
|
|
|
@ -384,8 +384,8 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy {
|
||||||
*/
|
*/
|
||||||
openCourseSummary(): void {
|
openCourseSummary(): void {
|
||||||
CoreNavigator.navigateToSitePath(
|
CoreNavigator.navigateToSitePath(
|
||||||
'/course/' + this.course.id + '/preview',
|
`/course/${this.course.id}/preview`,
|
||||||
{ params: { course: this.course } },
|
{ params: { course: this.course, avoidOpenCourse: true } },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -249,7 +249,7 @@ export class CoreCourseIndexPage implements OnInit, OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
CoreNavigator.navigateToSitePath(
|
CoreNavigator.navigateToSitePath(
|
||||||
'/course/' + this.course.id + '/preview',
|
`/course/${this.course.id}/preview`,
|
||||||
{ params: { course: this.course, avoidOpenCourse: true } },
|
{ params: { course: this.course, avoidOpenCourse: true } },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
</ion-buttons>
|
</ion-buttons>
|
||||||
<ion-title>
|
<ion-title>
|
||||||
<h1>
|
<h1>
|
||||||
<core-format-text [text]="course?.fullname" contextLevel="course" [contextInstanceId]="course?.id"></core-format-text>
|
{{'core.course.coursesummary' | translate}}
|
||||||
</h1>
|
</h1>
|
||||||
</ion-title>
|
</ion-title>
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
|
@ -16,44 +16,49 @@
|
||||||
</ion-refresher>
|
</ion-refresher>
|
||||||
<core-loading [hideUntil]="dataLoaded">
|
<core-loading [hideUntil]="dataLoaded">
|
||||||
<div *ngIf="courseImageUrl" class="core-course-thumb-parallax">
|
<div *ngIf="courseImageUrl" class="core-course-thumb-parallax">
|
||||||
<div (click)="openCourse()" class="core-course-thumb">
|
<div class="core-course-thumb">
|
||||||
<img [src]="courseImageUrl" core-external-content alt="" />
|
<img [src]="courseImageUrl" core-external-content alt="" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="core-course-thumb-parallax-content" *ngIf="course">
|
<div class="core-course-thumb-parallax-content" *ngIf="course">
|
||||||
<ion-item class="ion-text-wrap" (click)="openCourse()" [attr.aria-label]="course.fullname" [detail]="canAccessCourse"
|
<ion-item class="ion-text-wrap">
|
||||||
[button]="canAccessCourse">
|
|
||||||
<ion-icon name="fas-graduation-cap" fixed-width slot="start" aria-hidden="true"></ion-icon>
|
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<h2>
|
|
||||||
<core-format-text [text]="course.fullname" contextLevel="course" [contextInstanceId]="course.id">
|
|
||||||
</core-format-text>
|
|
||||||
</h2>
|
|
||||||
<p *ngIf="course.categoryname">
|
<p *ngIf="course.categoryname">
|
||||||
<core-format-text [text]="course.categoryname" contextLevel="coursecat" [contextInstanceId]="course.categoryid">
|
<core-format-text [text]="course.categoryname" contextLevel="coursecat" [contextInstanceId]="course.categoryid">
|
||||||
</core-format-text>
|
</core-format-text>
|
||||||
</p>
|
</p>
|
||||||
|
<h2>
|
||||||
|
<core-format-text [text]="course.fullname" contextLevel="course" [contextInstanceId]="course.id">
|
||||||
|
</core-format-text>
|
||||||
|
</h2>
|
||||||
<p *ngIf="course.startdate">
|
<p *ngIf="course.startdate">
|
||||||
{{course.startdate * 1000 | coreFormatDate:"strftimedatefullshort" }}
|
{{course.startdate * 1000 | coreFormatDate:"strftimedatefullshort" }}
|
||||||
<span *ngIf="course.enddate"> - {{course.enddate * 1000 | coreFormatDate:"strftimedatefullshort" }}</span>
|
<span *ngIf="course.enddate"> - {{course.enddate * 1000 | coreFormatDate:"strftimedatefullshort" }}</span>
|
||||||
</p>
|
</p>
|
||||||
|
<div class="core-course-progress" *ngIf="progress !== undefined">
|
||||||
|
<core-progress-bar [progress]="progress" a11yText="core.course.aria:sectionprogress">
|
||||||
|
</core-progress-bar>
|
||||||
|
</div>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<ion-item class="ion-text-wrap" *ngIf="course.summary" detail="false">
|
<ion-item class="ion-text-wrap" *ngIf="course.summary" detail="false">
|
||||||
<ion-label>
|
<ion-label>
|
||||||
|
<p class="item-heading">
|
||||||
|
{{'core.summary' | translate}}
|
||||||
|
</p>
|
||||||
<core-format-text [text]="course.summary" [maxHeight]="120" contextLevel="course" [contextInstanceId]="course.id">
|
<core-format-text [text]="course.summary" [maxHeight]="120" contextLevel="course" [contextInstanceId]="course.id">
|
||||||
</core-format-text>
|
</core-format-text>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<ng-container class="ion-text-wrap" *ngIf="course.contacts && course.contacts.length">
|
<ion-list *ngIf="course.contacts && course.contacts.length">
|
||||||
<ion-item-divider>
|
<ion-item-divider class="ion-text-wrap">
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<h2>{{ 'core.teachers' | translate }}</h2>
|
<h2>{{ 'core.teachers' | translate }}</h2>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item-divider>
|
</ion-item-divider>
|
||||||
<ion-item class="ion-text-wrap" *ngFor="let contact of course.contacts" core-user-link [userId]="contact.id"
|
<ion-item button class="ion-text-wrap" *ngFor="let contact of course.contacts" core-user-link [userId]="contact.id"
|
||||||
[courseId]="isEnrolled ? course.id : null" [attr.aria-label]="'core.viewprofile' | translate" detail="true">
|
[courseId]="isEnrolled ? course.id : null" [attr.aria-label]="'core.viewprofile' | translate" detail="true">
|
||||||
<core-user-avatar [user]="contact" slot="start" [userId]="contact.id" [courseId]="isEnrolled ? course.id : null">
|
<core-user-avatar [user]="contact" slot="start" [userId]="contact.id" [courseId]="isEnrolled ? course.id : null">
|
||||||
</core-user-avatar>
|
</core-user-avatar>
|
||||||
|
@ -62,7 +67,7 @@
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<core-spacer></core-spacer>
|
<core-spacer></core-spacer>
|
||||||
</ng-container>
|
</ion-list>
|
||||||
|
|
||||||
<ion-item class="ion-text-wrap" *ngIf="course.customfields">
|
<ion-item class="ion-text-wrap" *ngIf="course.customfields">
|
||||||
<ion-label>
|
<ion-label>
|
||||||
|
@ -83,7 +88,8 @@
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<div *ngIf="!isEnrolled" detail="false">
|
<!-- Enrol -->
|
||||||
|
<ng-container *ngIf="!isEnrolled">
|
||||||
<ion-item class="ion-text-wrap" *ngFor="let instance of selfEnrolInstances">
|
<ion-item class="ion-text-wrap" *ngFor="let instance of selfEnrolInstances">
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<p class="item-heading">{{ instance.name }}</p>
|
<p class="item-heading">{{ instance.name }}</p>
|
||||||
|
@ -92,23 +98,24 @@
|
||||||
</ion-button>
|
</ion-button>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</div>
|
<ion-item class="ion-text-wrap" *ngIf="paypalEnabled">
|
||||||
<ion-item class="ion-text-wrap" *ngIf="!isEnrolled && paypalEnabled">
|
<ion-label>
|
||||||
<ion-label>
|
<p class="item-heading">{{ 'core.courses.paypalaccepted' | translate }}</p>
|
||||||
<h2>{{ 'core.courses.paypalaccepted' | translate }}</h2>
|
<p *ngIf="isMobile">{{ 'core.paymentinstant' | translate }}</p>
|
||||||
<p>{{ 'core.paymentinstant' | translate }}</p>
|
<ion-button *ngIf="isMobile" expand="block" class="ion-margin-top" (click)="paypalEnrol()">
|
||||||
<ion-button expand="block" class="ion-margin-top" (click)="paypalEnrol()" *ngIf="isMobile">
|
{{ 'core.courses.sendpaymentbutton' | translate }}
|
||||||
{{ 'core.courses.sendpaymentbutton' | translate }}
|
</ion-button>
|
||||||
</ion-button>
|
</ion-label>
|
||||||
</ion-label>
|
</ion-item>
|
||||||
</ion-item>
|
<ion-item *ngIf="!selfEnrolInstances.length && !paypalEnabled">
|
||||||
<ion-item *ngIf="!isEnrolled && !selfEnrolInstances.length && !paypalEnabled">
|
<ion-label>
|
||||||
<ion-label>
|
<p class="item-heading">{{ 'core.courses.notenrollable' | translate }}</p>
|
||||||
<p>{{ 'core.courses.notenrollable' | translate }}</p>
|
</ion-label>
|
||||||
</ion-label>
|
</ion-item>
|
||||||
</ion-item>
|
</ng-container>
|
||||||
<ion-item *ngIf="canAccessCourse && downloadCourseEnabled" (click)="prefetchCourse()" detail="false"
|
|
||||||
[attr.aria-label]="prefetchCourseData.statusTranslatable | translate" button>
|
<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"
|
<ion-icon *ngIf="(prefetchCourseData.status != statusDownloaded) && !prefetchCourseData.loading"
|
||||||
[name]="prefetchCourseData.icon" slot="start" aria-hidden="true">
|
[name]="prefetchCourseData.icon" slot="start" aria-hidden="true">
|
||||||
</ion-icon>
|
</ion-icon>
|
||||||
|
@ -116,23 +123,24 @@
|
||||||
[name]="prefetchCourseData.icon" color="success" aria-hidden="true" role="status">
|
[name]="prefetchCourseData.icon" color="success" aria-hidden="true" role="status">
|
||||||
</ion-icon>
|
</ion-icon>
|
||||||
<ion-spinner *ngIf="prefetchCourseData.loading" slot="start" [attr.aria-label]="'core.loading' | translate"></ion-spinner>
|
<ion-spinner *ngIf="prefetchCourseData.loading" slot="start" [attr.aria-label]="'core.loading' | translate"></ion-spinner>
|
||||||
<ion-label>
|
<ion-label *ngIf="prefetchCourseData.status != statusDownloaded">{{ 'core.course.downloadcourse' | translate }}</ion-label>
|
||||||
<h2 *ngIf="prefetchCourseData.status != statusDownloaded">{{ 'core.course.downloadcourse' | translate }}</h2>
|
<ion-label *ngIf="prefetchCourseData.status == statusDownloaded">{{ 'core.course.refreshcourse' | translate }}</ion-label>
|
||||||
<h2 *ngIf="prefetchCourseData.status == statusDownloaded">{{ 'core.course.refreshcourse' | translate }}</h2>
|
</ion-button>
|
||||||
</ion-label>
|
|
||||||
</ion-item>
|
<ion-button class="ion-margin" (click)="openCourse()" *ngIf="!avoidOpenCourse && canAccessCourse" expand="block">
|
||||||
<ion-item button (click)="openCourse()" [attr.aria-label]="course.fullname" *ngIf="canAccessCourse" detail="true">
|
|
||||||
<ion-icon name="fas-briefcase" slot="start" aria-hidden="true"></ion-icon>
|
<ion-icon name="fas-briefcase" slot="start" aria-hidden="true"></ion-icon>
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<h2>{{ 'core.course' | translate }}</h2>
|
{{ 'core.course' | translate }}
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-button>
|
||||||
<ion-item [href]="courseUrl" core-link [attr.aria-label]="course.fullname" button detail="false" [showBrowserWarning]="false">
|
|
||||||
|
<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-icon name="fas-external-link-alt" slot="start" aria-hidden="true"></ion-icon>
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<h2>{{ 'core.openinbrowser' | translate }}</h2>
|
{{ 'core.openinbrowser' | translate }}
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</core-loading>
|
</core-loading>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
|
|
|
@ -20,8 +20,8 @@ import { CoreSites } from '@services/sites';
|
||||||
import { CoreDomUtils } from '@services/utils/dom';
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
import { CoreTextUtils } from '@services/utils/text';
|
import { CoreTextUtils } from '@services/utils/text';
|
||||||
import {
|
import {
|
||||||
|
CoreCourseCustomField,
|
||||||
CoreCourseEnrolmentMethod,
|
CoreCourseEnrolmentMethod,
|
||||||
CoreCourseGetCoursesData,
|
|
||||||
CoreCourses,
|
CoreCourses,
|
||||||
CoreCourseSearchedData,
|
CoreCourseSearchedData,
|
||||||
CoreCoursesProvider,
|
CoreCoursesProvider,
|
||||||
|
@ -34,6 +34,8 @@ import { Translate } from '@singletons';
|
||||||
import { CoreConstants } from '@/core/constants';
|
import { CoreConstants } from '@/core/constants';
|
||||||
import { CoreCoursesSelfEnrolPasswordComponent } from '../../../courses/components/self-enrol-password/self-enrol-password';
|
import { CoreCoursesSelfEnrolPasswordComponent } from '../../../courses/components/self-enrol-password/self-enrol-password';
|
||||||
import { CoreNavigator } from '@services/navigator';
|
import { CoreNavigator } from '@services/navigator';
|
||||||
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
|
import { CoreCourseWithImageAndColor } from '@features/courses/services/courses-helper';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Page that allows "previewing" a course and enrolling in it if enabled and not enrolled.
|
* Page that allows "previewing" a course and enrolling in it if enabled and not enrolled.
|
||||||
|
@ -45,12 +47,13 @@ import { CoreNavigator } from '@services/navigator';
|
||||||
})
|
})
|
||||||
export class CoreCoursePreviewPage implements OnInit, OnDestroy {
|
export class CoreCoursePreviewPage implements OnInit, OnDestroy {
|
||||||
|
|
||||||
course?: CoreCourseSearchedData;
|
course?: CoreCourseSummaryData;
|
||||||
isEnrolled = false;
|
isEnrolled = false;
|
||||||
canAccessCourse = true;
|
canAccessCourse = true;
|
||||||
selfEnrolInstances: CoreCourseEnrolmentMethod[] = [];
|
selfEnrolInstances: CoreCourseEnrolmentMethod[] = [];
|
||||||
paypalEnabled = false;
|
paypalEnabled = false;
|
||||||
dataLoaded = false;
|
dataLoaded = false;
|
||||||
|
avoidOpenCourse = false;
|
||||||
prefetchCourseData: CorePrefetchStatusInfo = {
|
prefetchCourseData: CorePrefetchStatusInfo = {
|
||||||
icon: '',
|
icon: '',
|
||||||
statusTranslatable: 'core.loading',
|
statusTranslatable: 'core.loading',
|
||||||
|
@ -64,6 +67,7 @@ export class CoreCoursePreviewPage implements OnInit, OnDestroy {
|
||||||
courseUrl = '';
|
courseUrl = '';
|
||||||
courseImageUrl?: string;
|
courseImageUrl?: string;
|
||||||
isMobile: boolean;
|
isMobile: boolean;
|
||||||
|
progress?: number;
|
||||||
|
|
||||||
protected isGuestEnabled = false;
|
protected isGuestEnabled = false;
|
||||||
protected useGuestAccess = false;
|
protected useGuestAccess = false;
|
||||||
|
@ -74,6 +78,7 @@ export class CoreCoursePreviewPage implements OnInit, OnDestroy {
|
||||||
protected paypalReturnUrl = '';
|
protected paypalReturnUrl = '';
|
||||||
protected pageDestroyed = false;
|
protected pageDestroyed = false;
|
||||||
protected courseStatusObserver?: CoreEventObserver;
|
protected courseStatusObserver?: CoreEventObserver;
|
||||||
|
protected courseId!: number;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected zone: NgZone,
|
protected zone: NgZone,
|
||||||
|
@ -84,7 +89,7 @@ export class CoreCoursePreviewPage implements OnInit, OnDestroy {
|
||||||
if (this.downloadCourseEnabled) {
|
if (this.downloadCourseEnabled) {
|
||||||
// Listen for status change in course.
|
// Listen for status change in course.
|
||||||
this.courseStatusObserver = CoreEvents.on(CoreEvents.COURSE_STATUS_CHANGED, (data) => {
|
this.courseStatusObserver = CoreEvents.on(CoreEvents.COURSE_STATUS_CHANGED, (data) => {
|
||||||
if (data.courseId == this.course!.id || data.courseId == CoreCourseProvider.ALL_COURSES_CLEARED) {
|
if (data.courseId == this.courseId || data.courseId == CoreCourseProvider.ALL_COURSES_CLEARED) {
|
||||||
this.updateCourseStatus(data.status);
|
this.updateCourseStatus(data.status);
|
||||||
}
|
}
|
||||||
}, CoreSites.getCurrentSiteId());
|
}, CoreSites.getCurrentSiteId());
|
||||||
|
@ -92,27 +97,25 @@ export class CoreCoursePreviewPage implements OnInit, OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* View loaded.
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
async ngOnInit(): Promise<void> {
|
async ngOnInit(): Promise<void> {
|
||||||
this.course = CoreNavigator.getRouteParam('course');
|
try {
|
||||||
|
this.courseId = CoreNavigator.getRequiredRouteNumberParam('courseId');
|
||||||
if (!this.course) {
|
} catch (error) {
|
||||||
|
CoreDomUtils.showErrorModal(error);
|
||||||
CoreNavigator.back();
|
CoreNavigator.back();
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentSite = CoreSites.getCurrentSite();
|
this.avoidOpenCourse = !!CoreNavigator.getRouteBooleanParam('avoidOpenCourse');
|
||||||
const currentSiteUrl = currentSite && currentSite.getURL();
|
this.course = CoreNavigator.getRouteParam('course');
|
||||||
|
|
||||||
this.paypalEnabled = this.course!.enrollmentmethods?.indexOf('paypal') > -1;
|
const currentSiteUrl = CoreSites.getRequiredCurrentSite().getURL();
|
||||||
this.enrolUrl = CoreTextUtils.concatenatePaths(currentSiteUrl!, 'enrol/index.php?id=' + this.course!.id);
|
this.enrolUrl = CoreTextUtils.concatenatePaths(currentSiteUrl, 'enrol/index.php?id=' + this.courseId);
|
||||||
this.courseUrl = CoreTextUtils.concatenatePaths(currentSiteUrl!, 'course/view.php?id=' + this.course!.id);
|
this.courseUrl = CoreTextUtils.concatenatePaths(currentSiteUrl, 'course/view.php?id=' + this.courseId);
|
||||||
this.paypalReturnUrl = CoreTextUtils.concatenatePaths(currentSiteUrl!, 'enrol/paypal/return.php');
|
this.paypalReturnUrl = CoreTextUtils.concatenatePaths(currentSiteUrl, 'enrol/paypal/return.php');
|
||||||
if (this.course.overviewfiles.length > 0) {
|
|
||||||
this.courseImageUrl = this.course.overviewfiles[0].fileurl;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.getCourse();
|
await this.getCourse();
|
||||||
|
@ -120,11 +123,11 @@ export class CoreCoursePreviewPage implements OnInit, OnDestroy {
|
||||||
if (this.downloadCourseEnabled) {
|
if (this.downloadCourseEnabled) {
|
||||||
|
|
||||||
// Determine course prefetch icon.
|
// Determine course prefetch icon.
|
||||||
this.prefetchCourseData = await CoreCourseHelper.getCourseStatusIconAndTitle(this.course!.id);
|
this.prefetchCourseData = await CoreCourseHelper.getCourseStatusIconAndTitle(this.courseId);
|
||||||
|
|
||||||
if (this.prefetchCourseData.loading) {
|
if (this.prefetchCourseData.loading) {
|
||||||
// Course is being downloaded. Get the download promise.
|
// Course is being downloaded. Get the download promise.
|
||||||
const promise = CoreCourseHelper.getCourseDownloadPromise(this.course!.id);
|
const promise = CoreCourseHelper.getCourseDownloadPromise(this.courseId);
|
||||||
if (promise) {
|
if (promise) {
|
||||||
// There is a download promise. If it fails, show an error.
|
// There is a download promise. If it fails, show an error.
|
||||||
promise.catch((error) => {
|
promise.catch((error) => {
|
||||||
|
@ -134,7 +137,7 @@ export class CoreCoursePreviewPage implements OnInit, OnDestroy {
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// No download, this probably means that the app was closed while downloading. Set previous status.
|
// No download, this probably means that the app was closed while downloading. Set previous status.
|
||||||
CoreCourse.setCoursePreviousStatus(this.course!.id);
|
CoreCourse.setCoursePreviousStatus(this.courseId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -177,13 +180,15 @@ export class CoreCoursePreviewPage implements OnInit, OnDestroy {
|
||||||
this.selfEnrolInstances = [];
|
this.selfEnrolInstances = [];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.enrolmentMethods = await CoreCourses.getCourseEnrolmentMethods(this.course!.id);
|
this.enrolmentMethods = await CoreCourses.getCourseEnrolmentMethods(this.courseId);
|
||||||
|
|
||||||
this.enrolmentMethods.forEach((method) => {
|
this.enrolmentMethods.forEach((method) => {
|
||||||
if (method.type === 'self') {
|
if (method.type === 'self') {
|
||||||
this.selfEnrolInstances.push(method);
|
this.selfEnrolInstances.push(method);
|
||||||
} else if (method.type === 'guest') {
|
} else if (method.type === 'guest') {
|
||||||
this.isGuestEnabled = true;
|
this.isGuestEnabled = true;
|
||||||
|
} else if (method.type === 'paypal') {
|
||||||
|
this.paypalEnabled = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -191,22 +196,17 @@ export class CoreCoursePreviewPage implements OnInit, OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let course: CoreEnrolledCourseData | CoreCourseGetCoursesData;
|
|
||||||
|
|
||||||
// Check if user is enrolled in the course.
|
// Check if user is enrolled in the course.
|
||||||
try {
|
try {
|
||||||
course = await CoreCourses.getUserCourse(this.course!.id);
|
this.course = await CoreCourses.getUserCourse(this.courseId);
|
||||||
this.isEnrolled = true;
|
this.isEnrolled = true;
|
||||||
} catch {
|
} catch {
|
||||||
// The user is not enrolled in the course. Use getCourses to see if it's an admin/manager and can see the course.
|
// The user is not enrolled in the course. Use getCourses to see if it's an admin/manager and can see the course.
|
||||||
this.isEnrolled = false;
|
this.isEnrolled = false;
|
||||||
|
this.course = await CoreCourses.getCourse(this.courseId);
|
||||||
course = await CoreCourses.getCourse(this.course!.id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Success retrieving the course, we can assume the user has permissions to view it.
|
// Success retrieving the course, we can assume the user has permissions to view it.
|
||||||
this.course!.fullname = course.fullname || this.course!.fullname;
|
|
||||||
this.course!.summary = course.summary || this.course!.summary;
|
|
||||||
this.canAccessCourse = true;
|
this.canAccessCourse = true;
|
||||||
this.useGuestAccess = false;
|
this.useGuestAccess = false;
|
||||||
} catch {
|
} catch {
|
||||||
|
@ -219,14 +219,37 @@ export class CoreCoursePreviewPage implements OnInit, OnDestroy {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!CoreSites.getCurrentSite()?.isVersionGreaterEqualThan('3.7')) {
|
if (this.course && 'overviewfiles' in this.course && this.course.overviewfiles?.length) {
|
||||||
try {
|
this.courseImageUrl = this.course.overviewfiles[0].fileurl;
|
||||||
const course = await CoreCourses.getCourseByField('id', this.course!.id);
|
}
|
||||||
|
|
||||||
this.course!.customfields = course.customfields;
|
try {
|
||||||
} catch {
|
const courseByField = await CoreCourses.getCourseByField('id', this.courseId);
|
||||||
// Ignore errors.
|
if (this.course) {
|
||||||
|
this.course.customfields = courseByField.customfields;
|
||||||
|
this.course.contacts = courseByField.contacts;
|
||||||
|
this.course.displayname = courseByField.displayname;
|
||||||
|
this.course.categoryname = courseByField.categoryname;
|
||||||
|
this.course.overviewfiles = courseByField.overviewfiles;
|
||||||
|
} else {
|
||||||
|
this.course = courseByField;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.paypalEnabled = !this.isEnrolled && courseByField.enrollmentmethods?.indexOf('paypal') > -1;
|
||||||
|
|
||||||
|
} catch {
|
||||||
|
// Ignore errors.
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.course ||
|
||||||
|
!('progress' in this.course) ||
|
||||||
|
typeof this.course.progress !== 'number' ||
|
||||||
|
this.course.progress < 0 ||
|
||||||
|
this.course.completionusertracked === false
|
||||||
|
) {
|
||||||
|
this.progress = undefined;
|
||||||
|
} else {
|
||||||
|
this.progress = this.course.progress;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.dataLoaded = true;
|
this.dataLoaded = true;
|
||||||
|
@ -234,13 +257,15 @@ export class CoreCoursePreviewPage implements OnInit, OnDestroy {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Open the course.
|
* Open the course.
|
||||||
|
*
|
||||||
|
* @param replaceCurrentPage If current place should be replaced in the navigation stack.
|
||||||
*/
|
*/
|
||||||
openCourse(): void {
|
openCourse(replaceCurrentPage = false): void {
|
||||||
if (!this.canAccessCourse) {
|
if (!this.canAccessCourse || !this.course || this.avoidOpenCourse) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
CoreCourseHelper.openCourse(this.course!, { isGuest: this.useGuestAccess });
|
CoreCourseHelper.openCourse(this.course, { params: { isGuest: this.useGuestAccess }, replace: replaceCurrentPage });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -279,7 +304,7 @@ export class CoreCoursePreviewPage implements OnInit, OnDestroy {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Open the enrolment page in InAppBrowser.
|
// Open the enrolment page in InAppBrowser.
|
||||||
const window = await CoreSites.getCurrentSite()!.openInAppWithAutoLogin(this.enrolUrl);
|
const window = await CoreSites.getRequiredCurrentSite().openInAppWithAutoLogin(this.enrolUrl);
|
||||||
|
|
||||||
// Observe loaded pages in the InAppBrowser to check if the enrol process has ended.
|
// Observe loaded pages in the InAppBrowser to check if the enrol process has ended.
|
||||||
const inAppLoadSubscription = window.on('loadstart').subscribe((event) => {
|
const inAppLoadSubscription = window.on('loadstart').subscribe((event) => {
|
||||||
|
@ -319,7 +344,7 @@ export class CoreCoursePreviewPage implements OnInit, OnDestroy {
|
||||||
const modal = await CoreDomUtils.showModalLoading('core.loading', true);
|
const modal = await CoreDomUtils.showModalLoading('core.loading', true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await CoreCourses.selfEnrol(this.course!.id, password, instanceId);
|
await CoreCourses.selfEnrol(this.courseId, password, instanceId);
|
||||||
|
|
||||||
// Close modal and refresh data.
|
// Close modal and refresh data.
|
||||||
this.isEnrolled = true;
|
this.isEnrolled = true;
|
||||||
|
@ -331,13 +356,13 @@ export class CoreCoursePreviewPage implements OnInit, OnDestroy {
|
||||||
await this.refreshData().finally(() => {
|
await this.refreshData().finally(() => {
|
||||||
// My courses have been updated, trigger event.
|
// My courses have been updated, trigger event.
|
||||||
CoreEvents.trigger(CoreCoursesProvider.EVENT_MY_COURSES_UPDATED, {
|
CoreEvents.trigger(CoreCoursesProvider.EVENT_MY_COURSES_UPDATED, {
|
||||||
courseId: this.course!.id,
|
courseId: this.courseId,
|
||||||
course: this.course,
|
course: this.course,
|
||||||
action: CoreCoursesProvider.ACTION_ENROL,
|
action: CoreCoursesProvider.ACTION_ENROL,
|
||||||
}, CoreSites.getCurrentSiteId());
|
}, CoreSites.getCurrentSiteId());
|
||||||
});
|
});
|
||||||
|
|
||||||
this.openCourse();
|
this.openCourse(true);
|
||||||
|
|
||||||
modal?.dismiss();
|
modal?.dismiss();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -378,12 +403,10 @@ export class CoreCoursePreviewPage implements OnInit, OnDestroy {
|
||||||
const promises: Promise<void>[] = [];
|
const promises: Promise<void>[] = [];
|
||||||
|
|
||||||
promises.push(CoreCourses.invalidateUserCourses());
|
promises.push(CoreCourses.invalidateUserCourses());
|
||||||
promises.push(CoreCourses.invalidateCourse(this.course!.id));
|
promises.push(CoreCourses.invalidateCourse(this.courseId));
|
||||||
promises.push(CoreCourses.invalidateCourseEnrolmentMethods(this.course!.id));
|
promises.push(CoreCourses.invalidateCourseEnrolmentMethods(this.courseId));
|
||||||
promises.push(CoreCourseOptionsDelegate.clearAndInvalidateCoursesOptions(this.course!.id));
|
promises.push(CoreCourseOptionsDelegate.clearAndInvalidateCoursesOptions(this.courseId));
|
||||||
if (CoreSites.getCurrentSite() && !CoreSites.getCurrentSite()!.isVersionGreaterEqualThan('3.7')) {
|
promises.push(CoreCourses.invalidateCoursesByField('id', this.courseId));
|
||||||
promises.push(CoreCourses.invalidateCoursesByField('id', this.course!.id));
|
|
||||||
}
|
|
||||||
if (this.guestInstanceId) {
|
if (this.guestInstanceId) {
|
||||||
promises.push(CoreCourses.invalidateCourseGuestEnrolmentInfo(this.guestInstanceId));
|
promises.push(CoreCourses.invalidateCourseGuestEnrolmentInfo(this.guestInstanceId));
|
||||||
}
|
}
|
||||||
|
@ -419,14 +442,10 @@ export class CoreCoursePreviewPage implements OnInit, OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if user is enrolled in the course.
|
// Check if user is enrolled in the course.
|
||||||
try {
|
await CoreUtils.ignoreErrors(CoreCourses.invalidateUserCourses());
|
||||||
CoreCourses.invalidateUserCourses();
|
|
||||||
} catch {
|
|
||||||
// Ignore errors.
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await CoreCourses.getUserCourse(this.course!.id);
|
await CoreCourses.getUserCourse(this.courseId);
|
||||||
} catch {
|
} catch {
|
||||||
// Not enrolled, wait a bit and try again.
|
// Not enrolled, wait a bit and try again.
|
||||||
if (this.pageDestroyed || (Date.now() - this.waitStart > 60000)) {
|
if (this.pageDestroyed || (Date.now() - this.waitStart > 60000)) {
|
||||||
|
@ -451,7 +470,7 @@ export class CoreCoursePreviewPage implements OnInit, OnDestroy {
|
||||||
*/
|
*/
|
||||||
async prefetchCourse(): Promise<void> {
|
async prefetchCourse(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
await CoreCourseHelper.confirmAndPrefetchCourse(this.prefetchCourseData, this.course!, {
|
await CoreCourseHelper.confirmAndPrefetchCourse(this.prefetchCourseData, this.course as CoreEnrolledCourseData, {
|
||||||
isGuest: this.useGuestAccess,
|
isGuest: this.useGuestAccess,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -462,14 +481,20 @@ export class CoreCoursePreviewPage implements OnInit, OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Page destroyed.
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
this.pageDestroyed = true;
|
this.pageDestroyed = true;
|
||||||
|
this.courseStatusObserver?.off();
|
||||||
if (this.courseStatusObserver) {
|
|
||||||
this.courseStatusObserver.off();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CoreCourseSummaryData = CoreCourseWithImageAndColor & (CoreEnrolledCourseData | CoreCourseSearchedData) & {
|
||||||
|
contacts?: { // Contact users.
|
||||||
|
id: number; // Contact user id.
|
||||||
|
fullname: string; // Contact user fullname.
|
||||||
|
}[];
|
||||||
|
customfields?: CoreCourseCustomField[]; // Custom fields and associated values.
|
||||||
|
categoryname?: string; // Category name.
|
||||||
|
};
|
||||||
|
|
|
@ -1,41 +1,43 @@
|
||||||
:host {
|
:host {
|
||||||
--scroll-factor: 0.5;
|
ion-content:not(.animating) {
|
||||||
--translate-z: calc(-2 * var(--scroll-factor))px;
|
&::part(scroll) {
|
||||||
--scale: calc(1 + var(--scroll-factor) * 2);
|
perspective: 1px;
|
||||||
|
perspective-origin: center top;
|
||||||
|
transform-style: preserve-3d;
|
||||||
|
}
|
||||||
|
|
||||||
perspective: 1px;
|
.core-course-thumb {
|
||||||
perspective-origin: center top;
|
transform-origin: center top;
|
||||||
transform-style: preserve-3d;
|
|
||||||
|
|
||||||
// @todo This parallax effect caused the image to be scaled during page transitions,
|
--scroll-factor: 0.5;
|
||||||
// and in some devices it seems like the problem persisted even after the transition.
|
--translate-z: calc(-2 * var(--scroll-factor))px;
|
||||||
// We should decide whether we want to keep this parallax or not, and if we do fix
|
--scale: calc(1 + var(--scroll-factor) * 2);
|
||||||
// the problem or find an alternative implementation. For now, it's disabled.
|
|
||||||
|
/**
|
||||||
|
* 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-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 {
|
.core-course-thumb {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
cursor: pointer;
|
|
||||||
pointer-events: auto;
|
|
||||||
transform-origin: center top;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculated with scroll-factor: 0.5;
|
|
||||||
* translate-z: -2 * $scroll-factor px;
|
|
||||||
* scale: 1 + $scroll-factor * 2;
|
|
||||||
*/
|
|
||||||
// transform: translateZ(-1px) scale(2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -68,7 +68,7 @@ import { CoreEventObserver, CoreEvents } from '@singletons/events';
|
||||||
import { CoreFilterHelper } from '@features/filter/services/filter-helper';
|
import { CoreFilterHelper } from '@features/filter/services/filter-helper';
|
||||||
import { CoreNetworkError } from '@classes/errors/network-error';
|
import { CoreNetworkError } from '@classes/errors/network-error';
|
||||||
import { CoreSiteHome } from '@features/sitehome/services/sitehome';
|
import { CoreSiteHome } from '@features/sitehome/services/sitehome';
|
||||||
import { CoreNavigator } from '@services/navigator';
|
import { CoreNavigationOptions, CoreNavigator } from '@services/navigator';
|
||||||
import { CoreSiteHomeHomeHandlerService } from '@features/sitehome/services/handlers/sitehome-home';
|
import { CoreSiteHomeHomeHandlerService } from '@features/sitehome/services/handlers/sitehome-home';
|
||||||
import { CoreStatusWithWarningsWSResponse } from '@services/ws';
|
import { CoreStatusWithWarningsWSResponse } from '@services/ws';
|
||||||
|
|
||||||
|
@ -1178,7 +1178,7 @@ export class CoreCourseHelperProvider {
|
||||||
|
|
||||||
modal?.dismiss();
|
modal?.dismiss();
|
||||||
|
|
||||||
return this.openCourse(course, params, siteId);
|
return this.openCourse(course, { params , siteId });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -2020,20 +2020,25 @@ export class CoreCourseHelperProvider {
|
||||||
* they will see the result immediately.
|
* they will see the result immediately.
|
||||||
*
|
*
|
||||||
* @param course Course to open
|
* @param course Course to open
|
||||||
* @param params Params to pass to the course page.
|
* @param navOptions Navigation options that includes params to pass to the page.
|
||||||
* @param siteId Site ID. If not defined, current site.
|
|
||||||
* @return Promise resolved when done.
|
* @return Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
async openCourse(course: CoreCourseAnyCourseData | { id: number }, params?: Params, siteId?: string): Promise<void> {
|
async openCourse(
|
||||||
|
course: CoreCourseAnyCourseData | { id: number },
|
||||||
|
navOptions?: CoreNavigationOptions & { siteId?: string },
|
||||||
|
): Promise<void> {
|
||||||
|
const siteId = navOptions?.siteId;
|
||||||
if (!siteId || siteId == CoreSites.getCurrentSiteId()) {
|
if (!siteId || siteId == CoreSites.getCurrentSiteId()) {
|
||||||
// Current site, we can open the course.
|
// Current site, we can open the course.
|
||||||
return CoreCourse.openCourse(course, params);
|
return CoreCourse.openCourse(course, navOptions);
|
||||||
} else {
|
} else {
|
||||||
// We need to load the site first.
|
// We need to load the site first.
|
||||||
params = params || {};
|
navOptions = navOptions || {};
|
||||||
Object.assign(params, { course: course });
|
|
||||||
|
|
||||||
await CoreNavigator.navigateToSitePath(`course/${course.id}`, { siteId, params });
|
navOptions.params = navOptions.params || {};
|
||||||
|
Object.assign(navOptions.params, { course: course });
|
||||||
|
|
||||||
|
await CoreNavigator.navigateToSitePath(`course/${course.id}`, navOptions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -43,7 +43,7 @@ import { CoreCourseLogCronHandler } from './handlers/log-cron';
|
||||||
import { CoreSitePlugins } from '@features/siteplugins/services/siteplugins';
|
import { CoreSitePlugins } from '@features/siteplugins/services/siteplugins';
|
||||||
import { CoreCourseAutoSyncData, CoreCourseSyncProvider } from './sync';
|
import { CoreCourseAutoSyncData, CoreCourseSyncProvider } from './sync';
|
||||||
import { CoreTagItem } from '@features/tag/services/tag';
|
import { CoreTagItem } from '@features/tag/services/tag';
|
||||||
import { CoreNavigator } from '@services/navigator';
|
import { CoreNavigationOptions, CoreNavigator } from '@services/navigator';
|
||||||
import { CoreCourseModuleDelegate } from './module-delegate';
|
import { CoreCourseModuleDelegate } from './module-delegate';
|
||||||
|
|
||||||
const ROOT_CACHE_KEY = 'mmCourse:';
|
const ROOT_CACHE_KEY = 'mmCourse:';
|
||||||
|
@ -1177,10 +1177,13 @@ export class CoreCourseProvider {
|
||||||
* This function must be in here instead of course helper to prevent circular dependencies.
|
* This function must be in here instead of course helper to prevent circular dependencies.
|
||||||
*
|
*
|
||||||
* @param course Course to open
|
* @param course Course to open
|
||||||
* @param params Other params to pass to the course page.
|
* @param navOptions Navigation options that includes params to pass to the page.
|
||||||
* @return Promise resolved when done.
|
* @return Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
async openCourse(course: CoreCourseAnyCourseData | { id: number }, params?: Params): Promise<void> {
|
async openCourse(
|
||||||
|
course: CoreCourseAnyCourseData | { id: number },
|
||||||
|
navOptions?: CoreNavigationOptions,
|
||||||
|
): Promise<void> {
|
||||||
const loading = await CoreDomUtils.showModalLoading();
|
const loading = await CoreDomUtils.showModalLoading();
|
||||||
|
|
||||||
// Wait for site plugins to be fetched.
|
// Wait for site plugins to be fetched.
|
||||||
|
@ -1197,7 +1200,7 @@ export class CoreCourseProvider {
|
||||||
if (!format || !CoreSitePlugins.sitePluginPromiseExists(`format_${format}`)) {
|
if (!format || !CoreSitePlugins.sitePluginPromiseExists(`format_${format}`)) {
|
||||||
// No custom format plugin. We don't need to wait for anything.
|
// No custom format plugin. We don't need to wait for anything.
|
||||||
loading.dismiss();
|
loading.dismiss();
|
||||||
await CoreCourseFormatDelegate.openCourse(<CoreCourseAnyCourseData> course, params);
|
await CoreCourseFormatDelegate.openCourse(<CoreCourseAnyCourseData> course, navOptions);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1208,7 +1211,7 @@ export class CoreCourseProvider {
|
||||||
|
|
||||||
// The format loaded successfully, but the handlers wont be registered until all site plugins have loaded.
|
// The format loaded successfully, but the handlers wont be registered until all site plugins have loaded.
|
||||||
if (CoreSitePlugins.sitePluginsFinishedLoading) {
|
if (CoreSitePlugins.sitePluginsFinishedLoading) {
|
||||||
return CoreCourseFormatDelegate.openCourse(<CoreCourseAnyCourseData> course, params);
|
return CoreCourseFormatDelegate.openCourse(<CoreCourseAnyCourseData> course, navOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for plugins to be loaded.
|
// Wait for plugins to be loaded.
|
||||||
|
@ -1217,7 +1220,7 @@ export class CoreCourseProvider {
|
||||||
const observer = CoreEvents.on(CoreEvents.SITE_PLUGINS_LOADED, () => {
|
const observer = CoreEvents.on(CoreEvents.SITE_PLUGINS_LOADED, () => {
|
||||||
observer?.off();
|
observer?.off();
|
||||||
|
|
||||||
CoreCourseFormatDelegate.openCourse(<CoreCourseAnyCourseData> course, params)
|
CoreCourseFormatDelegate.openCourse(<CoreCourseAnyCourseData> course, navOptions)
|
||||||
.then(deferred.resolve).catch(deferred.reject);
|
.then(deferred.resolve).catch(deferred.reject);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -13,10 +13,10 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { Injectable, Type } from '@angular/core';
|
import { Injectable, Type } from '@angular/core';
|
||||||
import { Params } from '@angular/router';
|
|
||||||
|
|
||||||
import { CoreDelegate, CoreDelegateHandler } from '@classes/delegate';
|
import { CoreDelegate, CoreDelegateHandler } from '@classes/delegate';
|
||||||
import { CoreCourseAnyCourseData } from '@features/courses/services/courses';
|
import { CoreCourseAnyCourseData } from '@features/courses/services/courses';
|
||||||
|
import { CoreNavigationOptions } from '@services/navigator';
|
||||||
import { makeSingleton } from '@singletons';
|
import { makeSingleton } from '@singletons';
|
||||||
import { CoreCourseWSSection } from './course';
|
import { CoreCourseWSSection } from './course';
|
||||||
import { CoreCourseSection } from './course-helper';
|
import { CoreCourseSection } from './course-helper';
|
||||||
|
@ -100,10 +100,10 @@ export interface CoreCourseFormatHandler extends CoreDelegateHandler {
|
||||||
* Your page should include the course handlers using CoreCoursesDelegate.
|
* Your page should include the course handlers using CoreCoursesDelegate.
|
||||||
*
|
*
|
||||||
* @param course The course to open. It should contain a "format" attribute.
|
* @param course The course to open. It should contain a "format" attribute.
|
||||||
* @param params Params to pass to the course page.
|
* @param navOptions Navigation options that includes params to pass to the page.
|
||||||
* @return Promise resolved when done.
|
* @return Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
openCourse?(course: CoreCourseAnyCourseData, params?: Params): Promise<void>;
|
openCourse?(course: CoreCourseAnyCourseData, navOptions?: CoreNavigationOptions): Promise<void>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the Component to use to display the course format instead of using the default one.
|
* Return the Component to use to display the course format instead of using the default one.
|
||||||
|
@ -323,11 +323,11 @@ export class CoreCourseFormatDelegateService extends CoreDelegate<CoreCourseForm
|
||||||
* Open a course. Should not be called directly. Call CoreCourseHelper.openCourse instead.
|
* Open a course. Should not be called directly. Call CoreCourseHelper.openCourse instead.
|
||||||
*
|
*
|
||||||
* @param course The course to open. It should contain a "format" attribute.
|
* @param course The course to open. It should contain a "format" attribute.
|
||||||
* @param params Params to pass to the course page.
|
* @param navOptions Navigation options that includes params to pass to the page.
|
||||||
* @return Promise resolved when done.
|
* @return Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
async openCourse(course: CoreCourseAnyCourseData, params?: Params): Promise<void> {
|
async openCourse(course: CoreCourseAnyCourseData, navOptions?: CoreNavigationOptions): Promise<void> {
|
||||||
await this.executeFunctionOnEnabled(course.format || '', 'openCourse', [course, params]);
|
await this.executeFunctionOnEnabled(course.format || '', 'openCourse', [course, navOptions]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -13,12 +13,9 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { Params } from '@angular/router';
|
|
||||||
|
|
||||||
import { CoreCourseAnyCourseData, CoreCourses } from '@features/courses/services/courses';
|
import { CoreCourseAnyCourseData, CoreCourses } from '@features/courses/services/courses';
|
||||||
import { CoreNavigator } from '@services/navigator';
|
import { CoreNavigationOptions, CoreNavigator } from '@services/navigator';
|
||||||
import { CoreUtils } from '@services/utils/utils';
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
import { CoreCourseWSSection } from '../course';
|
|
||||||
import { CoreCourseSection } from '../course-helper';
|
import { CoreCourseSection } from '../course-helper';
|
||||||
import { CoreCourseFormatHandler } from '../format-delegate';
|
import { CoreCourseFormatHandler } from '../format-delegate';
|
||||||
|
|
||||||
|
@ -32,19 +29,14 @@ export class CoreCourseFormatDefaultHandler implements CoreCourseFormatHandler {
|
||||||
format = 'default';
|
format = 'default';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether or not the handler is enabled on a site level.
|
* @inheritdoc
|
||||||
*
|
|
||||||
* @return Promise resolved with true if enabled.
|
|
||||||
*/
|
*/
|
||||||
async isEnabled(): Promise<boolean> {
|
async isEnabled(): Promise<boolean> {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the title to use in course page.
|
* @inheritdoc
|
||||||
*
|
|
||||||
* @param course The course.
|
|
||||||
* @return Title.
|
|
||||||
*/
|
*/
|
||||||
getCourseTitle(course: CoreCourseAnyCourseData): string {
|
getCourseTitle(course: CoreCourseAnyCourseData): string {
|
||||||
if (course.displayname) {
|
if (course.displayname) {
|
||||||
|
@ -57,57 +49,35 @@ export class CoreCourseFormatDefaultHandler implements CoreCourseFormatHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether it allows seeing all sections at the same time. Defaults to true.
|
* @inheritdoc
|
||||||
*
|
|
||||||
* @param course The course to check.
|
|
||||||
* @return Whether it can view all sections.
|
|
||||||
*/
|
*/
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
canViewAllSections(): boolean {
|
||||||
canViewAllSections(course: CoreCourseAnyCourseData): boolean {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the option blocks should be displayed. Defaults to true.
|
* @inheritdoc
|
||||||
*
|
|
||||||
* @param course The course to check.
|
|
||||||
* @return Whether it can display blocks.
|
|
||||||
*/
|
*/
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
displayBlocks(): boolean {
|
||||||
displayBlocks(course: CoreCourseAnyCourseData): boolean {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the default section selector should be displayed. Defaults to true.
|
* @inheritdoc
|
||||||
*
|
|
||||||
* @param course The course to check.
|
|
||||||
* @return Whether the default section selector should be displayed.
|
|
||||||
*/
|
*/
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
displaySectionSelector(): boolean {
|
||||||
displaySectionSelector(course: CoreCourseAnyCourseData): boolean {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the course refresher should be displayed. If it returns false, a refresher must be included in the course format,
|
* @inheritdoc
|
||||||
* and the doRefresh method of CoreCourseSectionPage must be called on refresh. Defaults to true.
|
|
||||||
*
|
|
||||||
* @param course The course to check.
|
|
||||||
* @param sections List of course sections.
|
|
||||||
* @return Whether the refresher should be displayed.
|
|
||||||
*/
|
*/
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
displayRefresher(): boolean {
|
||||||
displayRefresher(course: CoreCourseAnyCourseData, sections: CoreCourseWSSection[]): boolean {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a list of sections, get the "current" section that should be displayed first.
|
* @inheritdoc
|
||||||
*
|
|
||||||
* @param course The course to get the title.
|
|
||||||
* @param sections List of sections.
|
|
||||||
* @return Current section (or promise resolved with current section).
|
|
||||||
*/
|
*/
|
||||||
async getCurrentSection(course: CoreCourseAnyCourseData, sections: CoreCourseSection[]): Promise<CoreCourseSection> {
|
async getCurrentSection(course: CoreCourseAnyCourseData, sections: CoreCourseSection[]): Promise<CoreCourseSection> {
|
||||||
let marker: number | undefined;
|
let marker: number | undefined;
|
||||||
|
@ -137,48 +107,33 @@ export class CoreCourseFormatDefaultHandler implements CoreCourseFormatHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invalidate the data required to load the course format.
|
* @inheritdoc
|
||||||
*
|
|
||||||
* @param course The course to get the title.
|
|
||||||
* @param sections List of sections.
|
|
||||||
* @return Promise resolved when the data is invalidated.
|
|
||||||
*/
|
*/
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
async invalidateData(course: CoreCourseAnyCourseData): Promise<void> {
|
||||||
async invalidateData(course: CoreCourseAnyCourseData, sections: CoreCourseWSSection[]): Promise<void> {
|
|
||||||
await CoreCourses.invalidateCoursesByField('id', course.id);
|
await CoreCourses.invalidateCoursesByField('id', course.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Open the page to display a course. If not defined, the page CoreCourseSectionPage will be opened.
|
* @inheritdoc
|
||||||
* Implement it only if you want to create your own page to display the course. In general it's better to use the method
|
|
||||||
* getCourseFormatComponent because it will display the course handlers at the top.
|
|
||||||
* Your page should include the course handlers using CoreCoursesDelegate.
|
|
||||||
*
|
|
||||||
* @param course The course to open. It should contain a "format" attribute.
|
|
||||||
* @param params Params to pass to the course page.
|
|
||||||
* @return Promise resolved when done.
|
|
||||||
*/
|
*/
|
||||||
async openCourse(course: CoreCourseAnyCourseData, params?: Params): Promise<void> {
|
async openCourse(course: CoreCourseAnyCourseData, navOptions?: CoreNavigationOptions): Promise<void> {
|
||||||
params = params || {};
|
navOptions = navOptions || {};
|
||||||
Object.assign(params, { course: course });
|
|
||||||
|
navOptions.params = navOptions.params || {};
|
||||||
|
Object.assign(navOptions.params, { course: course });
|
||||||
|
|
||||||
// Don't return the .push promise, we don't want to display a loading modal during the page transition.
|
// Don't return the .push promise, we don't want to display a loading modal during the page transition.
|
||||||
const currentTab = CoreNavigator.getCurrentMainMenuTab();
|
const currentTab = CoreNavigator.getCurrentMainMenuTab();
|
||||||
const routeDepth = CoreNavigator.getRouteDepth(`/main/${currentTab}/course/${course.id}`);
|
const routeDepth = CoreNavigator.getRouteDepth(`/main/${currentTab}/course/${course.id}`);
|
||||||
const deepPath = '/deep'.repeat(routeDepth);
|
const deepPath = '/deep'.repeat(routeDepth);
|
||||||
|
|
||||||
CoreNavigator.navigateToSitePath(`course${deepPath}/${course.id}`, { params });
|
CoreNavigator.navigateToSitePath(`course${deepPath}/${course.id}`, navOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the view should be refreshed when completion changes. If your course format doesn't display
|
* @inheritdoc
|
||||||
* activity completion then you should return false.
|
|
||||||
*
|
|
||||||
* @param course The course.
|
|
||||||
* @return Whether course view should be refreshed when an activity completion changes.
|
|
||||||
*/
|
*/
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
async shouldRefreshWhenCompletionChanges(): Promise<boolean> {
|
||||||
async shouldRefreshWhenCompletionChanges(course: CoreCourseAnyCourseData): Promise<boolean> {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -171,7 +171,7 @@ export class CoreCoursesCourseListItemComponent implements OnInit, OnDestroy, On
|
||||||
CoreCourseHelper.openCourse(this.course);
|
CoreCourseHelper.openCourse(this.course);
|
||||||
} else {
|
} else {
|
||||||
CoreNavigator.navigateToSitePath(
|
CoreNavigator.navigateToSitePath(
|
||||||
'/course/' + this.course.id + '/preview',
|
`/course/${this.course.id}/preview`,
|
||||||
{ params: { course: this.course } },
|
{ params: { course: this.course } },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -156,7 +156,7 @@ export class CoreCoursesCourseLinkHandlerService extends CoreContentLinksHandler
|
||||||
modal.dismiss();
|
modal.dismiss();
|
||||||
|
|
||||||
// Now open the course.
|
// Now open the course.
|
||||||
CoreCourseHelper.openCourse(course, pageParams);
|
CoreCourseHelper.openCourse(course, { params: pageParams });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -301,6 +301,7 @@
|
||||||
"strftimetime24": "%H:%M",
|
"strftimetime24": "%H:%M",
|
||||||
"submit": "Submit",
|
"submit": "Submit",
|
||||||
"success": "Success",
|
"success": "Success",
|
||||||
|
"summary": "Summary",
|
||||||
"tablet": "Tablet",
|
"tablet": "Tablet",
|
||||||
"teachers": "Teachers",
|
"teachers": "Teachers",
|
||||||
"thereisdatatosync": "There are offline {{$a}} to be synchronised.",
|
"thereisdatatosync": "There are offline {{$a}} to be synchronised.",
|
||||||
|
|
|
@ -15,6 +15,7 @@ information provided here is intended especially for developers.
|
||||||
The function CoreUserDelegate.getProfileHandlersFor must now receive a context + contextId instead of a courseId.
|
The function CoreUserDelegate.getProfileHandlersFor must now receive a context + contextId instead of a courseId.
|
||||||
The user handler function isEnabledForCourse is now called isEnabledForContext and receives a context + contextId instead of a courseId.
|
The user handler function isEnabledForCourse is now called isEnabledForContext and receives a context + contextId instead of a courseId.
|
||||||
Some user handler's functions have also changed to accept context + contextId instead of a courseId: isEnabledForUser, getDisplayData, action.
|
Some user handler's functions have also changed to accept context + contextId instead of a courseId: isEnabledForUser, getDisplayData, action.
|
||||||
|
- CoreCourseHelperProvider.openCourse parameters changed, now it admits CoreNavigationOptions + siteId on the same object that includes Params passed to page.
|
||||||
|
|
||||||
=== 3.9.5 ===
|
=== 3.9.5 ===
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue