MOBILE-3915 course: Improve course summary page
parent
901a445408
commit
6b46f48b3c
|
@ -2257,6 +2257,7 @@
|
|||
"core.strftimetime24": "langconfig",
|
||||
"core.submit": "moodle",
|
||||
"core.success": "moodle",
|
||||
"core.summary": "moodle",
|
||||
"core.tablet": "local_moodlemobileapp",
|
||||
"core.tag.defautltagcoll": "tag",
|
||||
"core.tag.errorareanotsupported": "local_moodlemobileapp",
|
||||
|
|
|
@ -93,6 +93,7 @@ export const moodleTransitionAnimation = (navEl: HTMLElement, opts: TransitionOp
|
|||
}
|
||||
|
||||
rootAnimation.addAnimation(enteringContentAnimation);
|
||||
enteringContentAnimation.beforeAddClass('animating').afterRemoveClass('animating');
|
||||
|
||||
if (backDirection) {
|
||||
enteringContentAnimation
|
||||
|
@ -214,6 +215,8 @@ export const moodleTransitionAnimation = (navEl: HTMLElement, opts: TransitionOp
|
|||
// setup leaving view
|
||||
if (leavingEl) {
|
||||
const leavingContent = createAnimation();
|
||||
leavingContent.beforeAddClass('animating').afterRemoveClass('animating');
|
||||
|
||||
const leavingContentEl = leavingEl.querySelector(':scope > ion-content');
|
||||
const leavingToolBarEls = leavingEl.querySelectorAll(':scope > ion-header > ion-toolbar');
|
||||
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({
|
||||
selector: 'core-course-format-single-activity',
|
||||
templateUrl: 'core-course-format-single-activity.html',
|
||||
styleUrls: ['single-activity.scss'],
|
||||
})
|
||||
export class CoreCourseFormatSingleActivityComponent implements OnChanges {
|
||||
|
||||
|
|
|
@ -384,8 +384,8 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy {
|
|||
*/
|
||||
openCourseSummary(): void {
|
||||
CoreNavigator.navigateToSitePath(
|
||||
'/course/' + this.course.id + '/preview',
|
||||
{ params: { course: this.course } },
|
||||
`/course/${this.course.id}/preview`,
|
||||
{ params: { course: this.course, avoidOpenCourse: true } },
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -249,7 +249,7 @@ export class CoreCourseIndexPage implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
CoreNavigator.navigateToSitePath(
|
||||
'/course/' + this.course.id + '/preview',
|
||||
`/course/${this.course.id}/preview`,
|
||||
{ params: { course: this.course, avoidOpenCourse: true } },
|
||||
);
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
</ion-buttons>
|
||||
<ion-title>
|
||||
<h1>
|
||||
<core-format-text [text]="course?.fullname" contextLevel="course" [contextInstanceId]="course?.id"></core-format-text>
|
||||
{{'core.course.coursesummary' | translate}}
|
||||
</h1>
|
||||
</ion-title>
|
||||
</ion-toolbar>
|
||||
|
@ -16,44 +16,49 @@
|
|||
</ion-refresher>
|
||||
<core-loading [hideUntil]="dataLoaded">
|
||||
<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="" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="core-course-thumb-parallax-content" *ngIf="course">
|
||||
<ion-item class="ion-text-wrap" (click)="openCourse()" [attr.aria-label]="course.fullname" [detail]="canAccessCourse"
|
||||
[button]="canAccessCourse">
|
||||
<ion-icon name="fas-graduation-cap" fixed-width slot="start" aria-hidden="true"></ion-icon>
|
||||
<ion-item class="ion-text-wrap">
|
||||
<ion-label>
|
||||
<h2>
|
||||
<core-format-text [text]="course.fullname" contextLevel="course" [contextInstanceId]="course.id">
|
||||
</core-format-text>
|
||||
</h2>
|
||||
<p *ngIf="course.categoryname">
|
||||
<core-format-text [text]="course.categoryname" contextLevel="coursecat" [contextInstanceId]="course.categoryid">
|
||||
</core-format-text>
|
||||
</p>
|
||||
<h2>
|
||||
<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>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-item class="ion-text-wrap" *ngIf="course.summary" detail="false">
|
||||
<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>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ng-container class="ion-text-wrap" *ngIf="course.contacts && course.contacts.length">
|
||||
<ion-item-divider>
|
||||
<ion-list *ngIf="course.contacts && course.contacts.length">
|
||||
<ion-item-divider class="ion-text-wrap">
|
||||
<ion-label>
|
||||
<h2>{{ 'core.teachers' | translate }}</h2>
|
||||
</ion-label>
|
||||
</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">
|
||||
<core-user-avatar [user]="contact" slot="start" [userId]="contact.id" [courseId]="isEnrolled ? course.id : null">
|
||||
</core-user-avatar>
|
||||
|
@ -62,7 +67,7 @@
|
|||
</ion-label>
|
||||
</ion-item>
|
||||
<core-spacer></core-spacer>
|
||||
</ng-container>
|
||||
</ion-list>
|
||||
|
||||
<ion-item class="ion-text-wrap" *ngIf="course.customfields">
|
||||
<ion-label>
|
||||
|
@ -83,7 +88,8 @@
|
|||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<div *ngIf="!isEnrolled" detail="false">
|
||||
<!-- 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>
|
||||
|
@ -92,23 +98,24 @@
|
|||
</ion-button>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</div>
|
||||
<ion-item class="ion-text-wrap" *ngIf="!isEnrolled && paypalEnabled">
|
||||
<ion-item class="ion-text-wrap" *ngIf="paypalEnabled">
|
||||
<ion-label>
|
||||
<h2>{{ 'core.courses.paypalaccepted' | translate }}</h2>
|
||||
<p>{{ 'core.paymentinstant' | translate }}</p>
|
||||
<ion-button expand="block" class="ion-margin-top" (click)="paypalEnrol()" *ngIf="isMobile">
|
||||
<p class="item-heading">{{ 'core.courses.paypalaccepted' | translate }}</p>
|
||||
<p *ngIf="isMobile">{{ 'core.paymentinstant' | translate }}</p>
|
||||
<ion-button *ngIf="isMobile" expand="block" class="ion-margin-top" (click)="paypalEnrol()">
|
||||
{{ 'core.courses.sendpaymentbutton' | translate }}
|
||||
</ion-button>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item *ngIf="!isEnrolled && !selfEnrolInstances.length && !paypalEnabled">
|
||||
<ion-item *ngIf="!selfEnrolInstances.length && !paypalEnabled">
|
||||
<ion-label>
|
||||
<p>{{ 'core.courses.notenrollable' | translate }}</p>
|
||||
<p class="item-heading">{{ 'core.courses.notenrollable' | translate }}</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item *ngIf="canAccessCourse && downloadCourseEnabled" (click)="prefetchCourse()" detail="false"
|
||||
[attr.aria-label]="prefetchCourseData.statusTranslatable | translate" button>
|
||||
</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>
|
||||
|
@ -116,23 +123,24 @@
|
|||
[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>
|
||||
<h2 *ngIf="prefetchCourseData.status != statusDownloaded">{{ 'core.course.downloadcourse' | translate }}</h2>
|
||||
<h2 *ngIf="prefetchCourseData.status == statusDownloaded">{{ 'core.course.refreshcourse' | translate }}</h2>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item button (click)="openCourse()" [attr.aria-label]="course.fullname" *ngIf="canAccessCourse" detail="true">
|
||||
<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>
|
||||
<h2>{{ 'core.course' | translate }}</h2>
|
||||
{{ 'core.course' | translate }}
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item [href]="courseUrl" core-link [attr.aria-label]="course.fullname" button detail="false" [showBrowserWarning]="false">
|
||||
</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>
|
||||
<h2>{{ 'core.openinbrowser' | translate }}</h2>
|
||||
{{ 'core.openinbrowser' | translate }}
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-button>
|
||||
|
||||
</div>
|
||||
</core-loading>
|
||||
</ion-content>
|
||||
|
|
|
@ -20,8 +20,8 @@ import { CoreSites } from '@services/sites';
|
|||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { CoreTextUtils } from '@services/utils/text';
|
||||
import {
|
||||
CoreCourseCustomField,
|
||||
CoreCourseEnrolmentMethod,
|
||||
CoreCourseGetCoursesData,
|
||||
CoreCourses,
|
||||
CoreCourseSearchedData,
|
||||
CoreCoursesProvider,
|
||||
|
@ -34,6 +34,8 @@ import { Translate } from '@singletons';
|
|||
import { CoreConstants } from '@/core/constants';
|
||||
import { CoreCoursesSelfEnrolPasswordComponent } from '../../../courses/components/self-enrol-password/self-enrol-password';
|
||||
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.
|
||||
|
@ -45,12 +47,13 @@ import { CoreNavigator } from '@services/navigator';
|
|||
})
|
||||
export class CoreCoursePreviewPage implements OnInit, OnDestroy {
|
||||
|
||||
course?: CoreCourseSearchedData;
|
||||
course?: CoreCourseSummaryData;
|
||||
isEnrolled = false;
|
||||
canAccessCourse = true;
|
||||
selfEnrolInstances: CoreCourseEnrolmentMethod[] = [];
|
||||
paypalEnabled = false;
|
||||
dataLoaded = false;
|
||||
avoidOpenCourse = false;
|
||||
prefetchCourseData: CorePrefetchStatusInfo = {
|
||||
icon: '',
|
||||
statusTranslatable: 'core.loading',
|
||||
|
@ -64,6 +67,7 @@ export class CoreCoursePreviewPage implements OnInit, OnDestroy {
|
|||
courseUrl = '';
|
||||
courseImageUrl?: string;
|
||||
isMobile: boolean;
|
||||
progress?: number;
|
||||
|
||||
protected isGuestEnabled = false;
|
||||
protected useGuestAccess = false;
|
||||
|
@ -74,6 +78,7 @@ export class CoreCoursePreviewPage implements OnInit, OnDestroy {
|
|||
protected paypalReturnUrl = '';
|
||||
protected pageDestroyed = false;
|
||||
protected courseStatusObserver?: CoreEventObserver;
|
||||
protected courseId!: number;
|
||||
|
||||
constructor(
|
||||
protected zone: NgZone,
|
||||
|
@ -84,7 +89,7 @@ export class CoreCoursePreviewPage implements OnInit, OnDestroy {
|
|||
if (this.downloadCourseEnabled) {
|
||||
// Listen for status change in course.
|
||||
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);
|
||||
}
|
||||
}, CoreSites.getCurrentSiteId());
|
||||
|
@ -92,27 +97,25 @@ export class CoreCoursePreviewPage implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
/**
|
||||
* View loaded.
|
||||
* @inheritdoc
|
||||
*/
|
||||
async ngOnInit(): Promise<void> {
|
||||
this.course = CoreNavigator.getRouteParam('course');
|
||||
|
||||
if (!this.course) {
|
||||
try {
|
||||
this.courseId = CoreNavigator.getRequiredRouteNumberParam('courseId');
|
||||
} catch (error) {
|
||||
CoreDomUtils.showErrorModal(error);
|
||||
CoreNavigator.back();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const currentSite = CoreSites.getCurrentSite();
|
||||
const currentSiteUrl = currentSite && currentSite.getURL();
|
||||
this.avoidOpenCourse = !!CoreNavigator.getRouteBooleanParam('avoidOpenCourse');
|
||||
this.course = CoreNavigator.getRouteParam('course');
|
||||
|
||||
this.paypalEnabled = this.course!.enrollmentmethods?.indexOf('paypal') > -1;
|
||||
this.enrolUrl = CoreTextUtils.concatenatePaths(currentSiteUrl!, 'enrol/index.php?id=' + this.course!.id);
|
||||
this.courseUrl = CoreTextUtils.concatenatePaths(currentSiteUrl!, 'course/view.php?id=' + this.course!.id);
|
||||
this.paypalReturnUrl = CoreTextUtils.concatenatePaths(currentSiteUrl!, 'enrol/paypal/return.php');
|
||||
if (this.course.overviewfiles.length > 0) {
|
||||
this.courseImageUrl = this.course.overviewfiles[0].fileurl;
|
||||
}
|
||||
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);
|
||||
this.paypalReturnUrl = CoreTextUtils.concatenatePaths(currentSiteUrl, 'enrol/paypal/return.php');
|
||||
|
||||
try {
|
||||
await this.getCourse();
|
||||
|
@ -120,11 +123,11 @@ export class CoreCoursePreviewPage implements OnInit, OnDestroy {
|
|||
if (this.downloadCourseEnabled) {
|
||||
|
||||
// Determine course prefetch icon.
|
||||
this.prefetchCourseData = await CoreCourseHelper.getCourseStatusIconAndTitle(this.course!.id);
|
||||
this.prefetchCourseData = await CoreCourseHelper.getCourseStatusIconAndTitle(this.courseId);
|
||||
|
||||
if (this.prefetchCourseData.loading) {
|
||||
// Course is being downloaded. Get the download promise.
|
||||
const promise = CoreCourseHelper.getCourseDownloadPromise(this.course!.id);
|
||||
const promise = CoreCourseHelper.getCourseDownloadPromise(this.courseId);
|
||||
if (promise) {
|
||||
// There is a download promise. If it fails, show an error.
|
||||
promise.catch((error) => {
|
||||
|
@ -134,7 +137,7 @@ export class CoreCoursePreviewPage implements OnInit, OnDestroy {
|
|||
});
|
||||
} else {
|
||||
// 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 = [];
|
||||
|
||||
try {
|
||||
this.enrolmentMethods = await CoreCourses.getCourseEnrolmentMethods(this.course!.id);
|
||||
this.enrolmentMethods = await CoreCourses.getCourseEnrolmentMethods(this.courseId);
|
||||
|
||||
this.enrolmentMethods.forEach((method) => {
|
||||
if (method.type === 'self') {
|
||||
this.selfEnrolInstances.push(method);
|
||||
} else if (method.type === 'guest') {
|
||||
this.isGuestEnabled = true;
|
||||
} else if (method.type === 'paypal') {
|
||||
this.paypalEnabled = true;
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
|
@ -191,22 +196,17 @@ export class CoreCoursePreviewPage implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
try {
|
||||
let course: CoreEnrolledCourseData | CoreCourseGetCoursesData;
|
||||
|
||||
// Check if user is enrolled in the course.
|
||||
try {
|
||||
course = await CoreCourses.getUserCourse(this.course!.id);
|
||||
this.course = await CoreCourses.getUserCourse(this.courseId);
|
||||
this.isEnrolled = true;
|
||||
} catch {
|
||||
// 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;
|
||||
|
||||
course = await CoreCourses.getCourse(this.course!.id);
|
||||
this.course = await CoreCourses.getCourse(this.courseId);
|
||||
}
|
||||
|
||||
// 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.useGuestAccess = false;
|
||||
} catch {
|
||||
|
@ -219,14 +219,37 @@ export class CoreCoursePreviewPage implements OnInit, OnDestroy {
|
|||
}
|
||||
}
|
||||
|
||||
if (!CoreSites.getCurrentSite()?.isVersionGreaterEqualThan('3.7')) {
|
||||
try {
|
||||
const course = await CoreCourses.getCourseByField('id', this.course!.id);
|
||||
if (this.course && 'overviewfiles' in this.course && this.course.overviewfiles?.length) {
|
||||
this.courseImageUrl = this.course.overviewfiles[0].fileurl;
|
||||
}
|
||||
|
||||
try {
|
||||
const courseByField = await CoreCourses.getCourseByField('id', this.courseId);
|
||||
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;
|
||||
|
||||
this.course!.customfields = course.customfields;
|
||||
} 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;
|
||||
|
@ -234,13 +257,15 @@ export class CoreCoursePreviewPage implements OnInit, OnDestroy {
|
|||
|
||||
/**
|
||||
* Open the course.
|
||||
*
|
||||
* @param replaceCurrentPage If current place should be replaced in the navigation stack.
|
||||
*/
|
||||
openCourse(): void {
|
||||
if (!this.canAccessCourse) {
|
||||
openCourse(replaceCurrentPage = false): void {
|
||||
if (!this.canAccessCourse || !this.course || this.avoidOpenCourse) {
|
||||
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.
|
||||
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.
|
||||
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);
|
||||
|
||||
try {
|
||||
await CoreCourses.selfEnrol(this.course!.id, password, instanceId);
|
||||
await CoreCourses.selfEnrol(this.courseId, password, instanceId);
|
||||
|
||||
// Close modal and refresh data.
|
||||
this.isEnrolled = true;
|
||||
|
@ -331,13 +356,13 @@ export class CoreCoursePreviewPage implements OnInit, OnDestroy {
|
|||
await this.refreshData().finally(() => {
|
||||
// My courses have been updated, trigger event.
|
||||
CoreEvents.trigger(CoreCoursesProvider.EVENT_MY_COURSES_UPDATED, {
|
||||
courseId: this.course!.id,
|
||||
courseId: this.courseId,
|
||||
course: this.course,
|
||||
action: CoreCoursesProvider.ACTION_ENROL,
|
||||
}, CoreSites.getCurrentSiteId());
|
||||
});
|
||||
|
||||
this.openCourse();
|
||||
this.openCourse(true);
|
||||
|
||||
modal?.dismiss();
|
||||
} catch (error) {
|
||||
|
@ -378,12 +403,10 @@ export class CoreCoursePreviewPage implements OnInit, OnDestroy {
|
|||
const promises: Promise<void>[] = [];
|
||||
|
||||
promises.push(CoreCourses.invalidateUserCourses());
|
||||
promises.push(CoreCourses.invalidateCourse(this.course!.id));
|
||||
promises.push(CoreCourses.invalidateCourseEnrolmentMethods(this.course!.id));
|
||||
promises.push(CoreCourseOptionsDelegate.clearAndInvalidateCoursesOptions(this.course!.id));
|
||||
if (CoreSites.getCurrentSite() && !CoreSites.getCurrentSite()!.isVersionGreaterEqualThan('3.7')) {
|
||||
promises.push(CoreCourses.invalidateCoursesByField('id', this.course!.id));
|
||||
}
|
||||
promises.push(CoreCourses.invalidateCourse(this.courseId));
|
||||
promises.push(CoreCourses.invalidateCourseEnrolmentMethods(this.courseId));
|
||||
promises.push(CoreCourseOptionsDelegate.clearAndInvalidateCoursesOptions(this.courseId));
|
||||
promises.push(CoreCourses.invalidateCoursesByField('id', this.courseId));
|
||||
if (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.
|
||||
try {
|
||||
CoreCourses.invalidateUserCourses();
|
||||
} catch {
|
||||
// Ignore errors.
|
||||
}
|
||||
await CoreUtils.ignoreErrors(CoreCourses.invalidateUserCourses());
|
||||
|
||||
try {
|
||||
await CoreCourses.getUserCourse(this.course!.id);
|
||||
await CoreCourses.getUserCourse(this.courseId);
|
||||
} catch {
|
||||
// Not enrolled, wait a bit and try again.
|
||||
if (this.pageDestroyed || (Date.now() - this.waitStart > 60000)) {
|
||||
|
@ -451,7 +470,7 @@ export class CoreCoursePreviewPage implements OnInit, OnDestroy {
|
|||
*/
|
||||
async prefetchCourse(): Promise<void> {
|
||||
try {
|
||||
await CoreCourseHelper.confirmAndPrefetchCourse(this.prefetchCourseData, this.course!, {
|
||||
await CoreCourseHelper.confirmAndPrefetchCourse(this.prefetchCourseData, this.course as CoreEnrolledCourseData, {
|
||||
isGuest: this.useGuestAccess,
|
||||
});
|
||||
} catch (error) {
|
||||
|
@ -462,14 +481,20 @@ export class CoreCoursePreviewPage implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
/**
|
||||
* Page destroyed.
|
||||
* @inheritdoc
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.pageDestroyed = true;
|
||||
|
||||
if (this.courseStatusObserver) {
|
||||
this.courseStatusObserver.off();
|
||||
}
|
||||
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 {
|
||||
--scroll-factor: 0.5;
|
||||
--translate-z: calc(-2 * var(--scroll-factor))px;
|
||||
--scale: calc(1 + var(--scroll-factor) * 2);
|
||||
|
||||
ion-content:not(.animating) {
|
||||
&::part(scroll) {
|
||||
perspective: 1px;
|
||||
perspective-origin: center top;
|
||||
transform-style: preserve-3d;
|
||||
}
|
||||
|
||||
// @todo This parallax effect caused the image to be scaled during page transitions,
|
||||
// and in some devices it seems like the problem persisted even after the transition.
|
||||
// We should decide whether we want to keep this parallax or not, and if we do fix
|
||||
// the problem or find an alternative implementation. For now, it's disabled.
|
||||
|
||||
// .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;
|
||||
cursor: pointer;
|
||||
pointer-events: auto;
|
||||
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);
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -68,7 +68,7 @@ import { CoreEventObserver, CoreEvents } from '@singletons/events';
|
|||
import { CoreFilterHelper } from '@features/filter/services/filter-helper';
|
||||
import { CoreNetworkError } from '@classes/errors/network-error';
|
||||
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 { CoreStatusWithWarningsWSResponse } from '@services/ws';
|
||||
|
||||
|
@ -1178,7 +1178,7 @@ export class CoreCourseHelperProvider {
|
|||
|
||||
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.
|
||||
*
|
||||
* @param course Course to open
|
||||
* @param params Params to pass to the course page.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @param navOptions Navigation options that includes params to pass to the page.
|
||||
* @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()) {
|
||||
// Current site, we can open the course.
|
||||
return CoreCourse.openCourse(course, params);
|
||||
return CoreCourse.openCourse(course, navOptions);
|
||||
} else {
|
||||
// We need to load the site first.
|
||||
params = params || {};
|
||||
Object.assign(params, { course: course });
|
||||
navOptions = navOptions || {};
|
||||
|
||||
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 { CoreCourseAutoSyncData, CoreCourseSyncProvider } from './sync';
|
||||
import { CoreTagItem } from '@features/tag/services/tag';
|
||||
import { CoreNavigator } from '@services/navigator';
|
||||
import { CoreNavigationOptions, CoreNavigator } from '@services/navigator';
|
||||
import { CoreCourseModuleDelegate } from './module-delegate';
|
||||
|
||||
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.
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
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();
|
||||
|
||||
// Wait for site plugins to be fetched.
|
||||
|
@ -1197,7 +1200,7 @@ export class CoreCourseProvider {
|
|||
if (!format || !CoreSitePlugins.sitePluginPromiseExists(`format_${format}`)) {
|
||||
// No custom format plugin. We don't need to wait for anything.
|
||||
loading.dismiss();
|
||||
await CoreCourseFormatDelegate.openCourse(<CoreCourseAnyCourseData> course, params);
|
||||
await CoreCourseFormatDelegate.openCourse(<CoreCourseAnyCourseData> course, navOptions);
|
||||
|
||||
return;
|
||||
}
|
||||
|
@ -1208,7 +1211,7 @@ export class CoreCourseProvider {
|
|||
|
||||
// The format loaded successfully, but the handlers wont be registered until all site plugins have loaded.
|
||||
if (CoreSitePlugins.sitePluginsFinishedLoading) {
|
||||
return CoreCourseFormatDelegate.openCourse(<CoreCourseAnyCourseData> course, params);
|
||||
return CoreCourseFormatDelegate.openCourse(<CoreCourseAnyCourseData> course, navOptions);
|
||||
}
|
||||
|
||||
// Wait for plugins to be loaded.
|
||||
|
@ -1217,7 +1220,7 @@ export class CoreCourseProvider {
|
|||
const observer = CoreEvents.on(CoreEvents.SITE_PLUGINS_LOADED, () => {
|
||||
observer?.off();
|
||||
|
||||
CoreCourseFormatDelegate.openCourse(<CoreCourseAnyCourseData> course, params)
|
||||
CoreCourseFormatDelegate.openCourse(<CoreCourseAnyCourseData> course, navOptions)
|
||||
.then(deferred.resolve).catch(deferred.reject);
|
||||
});
|
||||
|
||||
|
|
|
@ -13,10 +13,10 @@
|
|||
// limitations under the License.
|
||||
|
||||
import { Injectable, Type } from '@angular/core';
|
||||
import { Params } from '@angular/router';
|
||||
|
||||
import { CoreDelegate, CoreDelegateHandler } from '@classes/delegate';
|
||||
import { CoreCourseAnyCourseData } from '@features/courses/services/courses';
|
||||
import { CoreNavigationOptions } from '@services/navigator';
|
||||
import { makeSingleton } from '@singletons';
|
||||
import { CoreCourseWSSection } from './course';
|
||||
import { CoreCourseSection } from './course-helper';
|
||||
|
@ -100,10 +100,10 @@ export interface CoreCourseFormatHandler extends CoreDelegateHandler {
|
|||
* 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.
|
||||
* @param navOptions Navigation options that includes params to pass to the page.
|
||||
* @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.
|
||||
|
@ -323,11 +323,11 @@ export class CoreCourseFormatDelegateService extends CoreDelegate<CoreCourseForm
|
|||
* 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 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.
|
||||
*/
|
||||
async openCourse(course: CoreCourseAnyCourseData, params?: Params): Promise<void> {
|
||||
await this.executeFunctionOnEnabled(course.format || '', 'openCourse', [course, params]);
|
||||
async openCourse(course: CoreCourseAnyCourseData, navOptions?: CoreNavigationOptions): Promise<void> {
|
||||
await this.executeFunctionOnEnabled(course.format || '', 'openCourse', [course, navOptions]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -13,12 +13,9 @@
|
|||
// limitations under the License.
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Params } from '@angular/router';
|
||||
|
||||
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 { CoreCourseWSSection } from '../course';
|
||||
import { CoreCourseSection } from '../course-helper';
|
||||
import { CoreCourseFormatHandler } from '../format-delegate';
|
||||
|
||||
|
@ -32,19 +29,14 @@ export class CoreCourseFormatDefaultHandler implements CoreCourseFormatHandler {
|
|||
format = 'default';
|
||||
|
||||
/**
|
||||
* Whether or not the handler is enabled on a site level.
|
||||
*
|
||||
* @return Promise resolved with true if enabled.
|
||||
* @inheritdoc
|
||||
*/
|
||||
async isEnabled(): Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the title to use in course page.
|
||||
*
|
||||
* @param course The course.
|
||||
* @return Title.
|
||||
* @inheritdoc
|
||||
*/
|
||||
getCourseTitle(course: CoreCourseAnyCourseData): string {
|
||||
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.
|
||||
*
|
||||
* @param course The course to check.
|
||||
* @return Whether it can view all sections.
|
||||
* @inheritdoc
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
canViewAllSections(course: CoreCourseAnyCourseData): boolean {
|
||||
canViewAllSections(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the option blocks should be displayed. Defaults to true.
|
||||
*
|
||||
* @param course The course to check.
|
||||
* @return Whether it can display blocks.
|
||||
* @inheritdoc
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
displayBlocks(course: CoreCourseAnyCourseData): boolean {
|
||||
displayBlocks(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the default section selector should be displayed. Defaults to true.
|
||||
*
|
||||
* @param course The course to check.
|
||||
* @return Whether the default section selector should be displayed.
|
||||
* @inheritdoc
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
displaySectionSelector(course: CoreCourseAnyCourseData): boolean {
|
||||
displaySectionSelector(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the course refresher should be displayed. If it returns false, a refresher must be included in the course format,
|
||||
* 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.
|
||||
* @inheritdoc
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
displayRefresher(course: CoreCourseAnyCourseData, sections: CoreCourseWSSection[]): boolean {
|
||||
displayRefresher(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a list of sections, get the "current" section that should be displayed first.
|
||||
*
|
||||
* @param course The course to get the title.
|
||||
* @param sections List of sections.
|
||||
* @return Current section (or promise resolved with current section).
|
||||
* @inheritdoc
|
||||
*/
|
||||
async getCurrentSection(course: CoreCourseAnyCourseData, sections: CoreCourseSection[]): Promise<CoreCourseSection> {
|
||||
let marker: number | undefined;
|
||||
|
@ -137,48 +107,33 @@ export class CoreCourseFormatDefaultHandler implements CoreCourseFormatHandler {
|
|||
}
|
||||
|
||||
/**
|
||||
* Invalidate the data required to load the course format.
|
||||
*
|
||||
* @param course The course to get the title.
|
||||
* @param sections List of sections.
|
||||
* @return Promise resolved when the data is invalidated.
|
||||
* @inheritdoc
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
async invalidateData(course: CoreCourseAnyCourseData, sections: CoreCourseWSSection[]): Promise<void> {
|
||||
async invalidateData(course: CoreCourseAnyCourseData): Promise<void> {
|
||||
await CoreCourses.invalidateCoursesByField('id', course.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the page to display a course. If not defined, the page CoreCourseSectionPage will be opened.
|
||||
* 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.
|
||||
* @inheritdoc
|
||||
*/
|
||||
async openCourse(course: CoreCourseAnyCourseData, params?: Params): Promise<void> {
|
||||
params = params || {};
|
||||
Object.assign(params, { course: course });
|
||||
async openCourse(course: CoreCourseAnyCourseData, navOptions?: CoreNavigationOptions): Promise<void> {
|
||||
navOptions = navOptions || {};
|
||||
|
||||
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.
|
||||
const currentTab = CoreNavigator.getCurrentMainMenuTab();
|
||||
const routeDepth = CoreNavigator.getRouteDepth(`/main/${currentTab}/course/${course.id}`);
|
||||
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
|
||||
* activity completion then you should return false.
|
||||
*
|
||||
* @param course The course.
|
||||
* @return Whether course view should be refreshed when an activity completion changes.
|
||||
* @inheritdoc
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
async shouldRefreshWhenCompletionChanges(course: CoreCourseAnyCourseData): Promise<boolean> {
|
||||
async shouldRefreshWhenCompletionChanges(): Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -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}/preview`,
|
||||
{ params: { course: this.course } },
|
||||
);
|
||||
}
|
||||
|
|
|
@ -156,7 +156,7 @@ export class CoreCoursesCourseLinkHandlerService extends CoreContentLinksHandler
|
|||
modal.dismiss();
|
||||
|
||||
// Now open the course.
|
||||
CoreCourseHelper.openCourse(course, pageParams);
|
||||
CoreCourseHelper.openCourse(course, { params: pageParams });
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -301,6 +301,7 @@
|
|||
"strftimetime24": "%H:%M",
|
||||
"submit": "Submit",
|
||||
"success": "Success",
|
||||
"summary": "Summary",
|
||||
"tablet": "Tablet",
|
||||
"teachers": "Teachers",
|
||||
"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 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.
|
||||
- CoreCourseHelperProvider.openCourse parameters changed, now it admits CoreNavigationOptions + siteId on the same object that includes Params passed to page.
|
||||
|
||||
=== 3.9.5 ===
|
||||
|
||||
|
|
Loading…
Reference in New Issue