MOBILE-3659 course: Implement course format delegate
parent
94b8707ce6
commit
c617ec2dce
|
@ -23,12 +23,9 @@ import { CoreFilepool } from '@services/filepool';
|
||||||
import { CoreDomUtils } from '@services/utils/dom';
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
import { CoreUtils } from '@services/utils/utils';
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
import {
|
import {
|
||||||
|
CoreCourseAnyCourseData,
|
||||||
CoreCourseBasicData,
|
CoreCourseBasicData,
|
||||||
CoreCourseGetCoursesData,
|
|
||||||
CoreCourses,
|
CoreCourses,
|
||||||
CoreCourseSearchedData,
|
|
||||||
CoreEnrolledCourseBasicData,
|
|
||||||
CoreEnrolledCourseData,
|
|
||||||
} from '@features/courses/services/courses';
|
} from '@features/courses/services/courses';
|
||||||
import { CoreEnrolledCourseDataWithExtraInfoAndOptions } from '@features/courses/services/courses-helper';
|
import { CoreEnrolledCourseDataWithExtraInfoAndOptions } from '@features/courses/services/courses-helper';
|
||||||
import { CoreArray } from '@singletons/array';
|
import { CoreArray } from '@singletons/array';
|
||||||
|
@ -463,10 +460,10 @@ export class CoreCourseHelperProvider {
|
||||||
async getCourse(
|
async getCourse(
|
||||||
courseId: number,
|
courseId: number,
|
||||||
siteId?: string,
|
siteId?: string,
|
||||||
): Promise<{ enrolled: boolean; course: CoreEnrolledCourseData | CoreCourseSearchedData | CoreCourseGetCoursesData }> {
|
): Promise<{ enrolled: boolean; course: CoreCourseAnyCourseData }> {
|
||||||
siteId = siteId || CoreSites.instance.getCurrentSiteId();
|
siteId = siteId || CoreSites.instance.getCurrentSiteId();
|
||||||
|
|
||||||
let course: CoreEnrolledCourseData | CoreCourseSearchedData | CoreCourseGetCoursesData;
|
let course: CoreCourseAnyCourseData;
|
||||||
|
|
||||||
// Try with enrolled courses first.
|
// Try with enrolled courses first.
|
||||||
try {
|
try {
|
||||||
|
@ -495,11 +492,12 @@ export class CoreCourseHelperProvider {
|
||||||
* @param courseId Course ID.
|
* @param courseId Course ID.
|
||||||
* @param params Other params to pass to the course page.
|
* @param params Other params to pass to the course page.
|
||||||
* @param siteId Site ID. If not defined, current site.
|
* @param siteId Site ID. If not defined, current site.
|
||||||
|
* @return Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
async getAndOpenCourse(courseId: number, params?: Params, siteId?: string): Promise<any> {
|
async getAndOpenCourse(courseId: number, params?: Params, siteId?: string): Promise<void> {
|
||||||
const modal = await CoreDomUtils.instance.showModalLoading();
|
const modal = await CoreDomUtils.instance.showModalLoading();
|
||||||
|
|
||||||
let course: CoreEnrolledCourseData | CoreCourseSearchedData | CoreCourseGetCoursesData | { id: number };
|
let course: CoreCourseAnyCourseData | { id: number };
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const data = await this.getCourse(courseId, siteId);
|
const data = await this.getCourse(courseId, siteId);
|
||||||
|
@ -575,7 +573,7 @@ export class CoreCourseHelperProvider {
|
||||||
* @param siteId Site ID. If not defined, current site.
|
* @param siteId Site ID. If not defined, current site.
|
||||||
* @return Promise resolved when done.
|
* @return Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
async loadOfflineCompletion(courseId: number, sections: any[], siteId?: string): Promise<void> {
|
async loadOfflineCompletion(courseId: number, sections: CoreCourseSection[], siteId?: string): Promise<void> {
|
||||||
const offlineCompletions = await CoreCourseOffline.instance.getCourseManualCompletions(courseId, siteId);
|
const offlineCompletions = await CoreCourseOffline.instance.getCourseManualCompletions(courseId, siteId);
|
||||||
|
|
||||||
if (!offlineCompletions || !offlineCompletions.length) {
|
if (!offlineCompletions || !offlineCompletions.length) {
|
||||||
|
@ -602,7 +600,7 @@ export class CoreCourseHelperProvider {
|
||||||
offlineCompletion.timecompleted >= module.completiondata.timecompleted * 1000) {
|
offlineCompletion.timecompleted >= module.completiondata.timecompleted * 1000) {
|
||||||
// The module has offline completion. Load it.
|
// The module has offline completion. Load it.
|
||||||
module.completiondata.state = offlineCompletion.completed;
|
module.completiondata.state = offlineCompletion.completed;
|
||||||
module.completiondata.offline = true;
|
// @todo module.completiondata.offline = true;
|
||||||
|
|
||||||
// If all completions have been loaded, stop.
|
// If all completions have been loaded, stop.
|
||||||
loaded++;
|
loaded++;
|
||||||
|
@ -758,7 +756,7 @@ export class CoreCourseHelperProvider {
|
||||||
* @return Section download ID.
|
* @return Section download ID.
|
||||||
* @todo section type.
|
* @todo section type.
|
||||||
*/
|
*/
|
||||||
getSectionDownloadId(section: any): string {
|
getSectionDownloadId(section: CoreCourseSection): string {
|
||||||
return 'Section-' + section.id;
|
return 'Section-' + section.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -978,7 +976,7 @@ export class CoreCourseHelperProvider {
|
||||||
* @param siteId Site ID. If not defined, current site.
|
* @param siteId Site ID. If not defined, current site.
|
||||||
* @return Promise resolved when done.
|
* @return Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
async openCourse(course: CoreEnrolledCourseBasicData | { id: number }, params?: Params, siteId?: string): Promise<void> {
|
async openCourse(course: CoreCourseAnyCourseData | { id: number }, params?: Params, siteId?: string): Promise<void> {
|
||||||
if (!siteId || siteId == CoreSites.instance.getCurrentSiteId()) {
|
if (!siteId || siteId == CoreSites.instance.getCurrentSiteId()) {
|
||||||
// Current site, we can open the course.
|
// Current site, we can open the course.
|
||||||
return CoreCourse.instance.openCourse(course, params);
|
return CoreCourse.instance.openCourse(course, params);
|
||||||
|
|
|
@ -30,12 +30,14 @@ import { CoreCourseStatusDBRecord, COURSE_STATUS_TABLE } from './database/course
|
||||||
import { CoreCourseOffline } from './course-offline';
|
import { CoreCourseOffline } from './course-offline';
|
||||||
import { CoreError } from '@classes/errors/error';
|
import { CoreError } from '@classes/errors/error';
|
||||||
import {
|
import {
|
||||||
CoreCourses,
|
CoreCourseAnyCourseData,
|
||||||
CoreCoursesProvider,
|
CoreCoursesProvider,
|
||||||
} from '../../courses/services/courses';
|
} from '../../courses/services/courses';
|
||||||
import { CoreDomUtils } from '@services/utils/dom';
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
import { CoreWSError } from '@classes/errors/wserror';
|
import { CoreWSError } from '@classes/errors/wserror';
|
||||||
import { CorePushNotifications } from '@features/pushnotifications/services/pushnotifications';
|
import { CorePushNotifications } from '@features/pushnotifications/services/pushnotifications';
|
||||||
|
import { CoreCourseHelper } from './course-helper';
|
||||||
|
import { CoreCourseFormatDelegate } from './format-delegate';
|
||||||
|
|
||||||
const ROOT_CACHE_KEY = 'mmCourse:';
|
const ROOT_CACHE_KEY = 'mmCourse:';
|
||||||
|
|
||||||
|
@ -71,9 +73,6 @@ export class CoreCourseProvider {
|
||||||
protected logger: CoreLogger;
|
protected logger: CoreLogger;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
// @todo
|
|
||||||
// protected courseFormatDelegate: CoreCourseFormatDelegate,
|
|
||||||
// protected sitePluginsProvider: CoreSitePluginsProvider,
|
|
||||||
this.logger = CoreLogger.getInstance('CoreCourseProvider');
|
this.logger = CoreLogger.getInstance('CoreCourseProvider');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -981,39 +980,22 @@ export class CoreCourseProvider {
|
||||||
* @param params Other params to pass to the course page.
|
* @param params Other params to pass to the course page.
|
||||||
* @return Promise resolved when done.
|
* @return Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
async openCourse(
|
async openCourse(course: CoreCourseAnyCourseData | { id: number }, params?: Params): Promise<void> {
|
||||||
course: { id: number ; format?: string },
|
|
||||||
params?: Params, // eslint-disable-line @typescript-eslint/no-unused-vars
|
|
||||||
): Promise<void> {
|
|
||||||
// @todo const loading = await CoreDomUtils.instance.showModalLoading();
|
// @todo const loading = await CoreDomUtils.instance.showModalLoading();
|
||||||
|
|
||||||
// Wait for site plugins to be fetched.
|
// Wait for site plugins to be fetched.
|
||||||
// @todo await this.sitePluginsProvider.waitFetchPlugins();
|
// @todo await this.sitePluginsProvider.waitFetchPlugins();
|
||||||
|
|
||||||
if (typeof course.format == 'undefined') {
|
if (!('format' in course) || typeof course.format == 'undefined') {
|
||||||
// This block can be replaced by a call to CourseHelper.getCourse(), but it is circular dependant.
|
const result = await CoreCourseHelper.instance.getCourse(course.id);
|
||||||
const coursesProvider = CoreCourses.instance;
|
|
||||||
try {
|
course = result.course;
|
||||||
course = await coursesProvider.getUserCourse(course.id, true);
|
|
||||||
} catch (error) {
|
|
||||||
// Not enrolled or an error happened. Try to use another WebService.
|
|
||||||
const available = coursesProvider.isGetCoursesByFieldAvailableInSite();
|
|
||||||
try {
|
|
||||||
if (available) {
|
|
||||||
course = await coursesProvider.getCourseByField('id', course.id);
|
|
||||||
} else {
|
|
||||||
course = await coursesProvider.getCourse(course.id);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
// Ignore errors.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* @todo
|
/* @todo
|
||||||
if (!this.sitePluginsProvider.sitePluginPromiseExists('format_' + course.format)) {
|
if (!this.sitePluginsProvider.sitePluginPromiseExists('format_' + course.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.
|
||||||
await this.courseFormatDelegate.openCourse(course, params);
|
await CoreCourseFormatDelegate.instance.openCourse(course, params);
|
||||||
loading.dismiss();
|
loading.dismiss();
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
@ -1024,20 +1006,17 @@ export class CoreCourseProvider {
|
||||||
/* @todo await this.sitePluginsProvider.sitePluginLoaded('format_' + course.format);
|
/* @todo await this.sitePluginsProvider.sitePluginLoaded('format_' + course.format);
|
||||||
// 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 (this.sitePluginsProvider.sitePluginsFinishedLoading) {
|
if (this.sitePluginsProvider.sitePluginsFinishedLoading) {
|
||||||
return this.courseFormatDelegate.openCourse(course, params);
|
return CoreCourseFormatDelegate.instance.openCourse(course, params);
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
// Wait for plugins to be loaded.
|
// Wait for plugins to be loaded.
|
||||||
const deferred = CoreUtils.instance.promiseDefer<void>();
|
const deferred = CoreUtils.instance.promiseDefer<void>();
|
||||||
|
|
||||||
const observer = CoreEvents.on(CoreEvents.SITE_PLUGINS_LOADED, () => {
|
const observer = CoreEvents.on(CoreEvents.SITE_PLUGINS_LOADED, () => {
|
||||||
observer && observer.off();
|
observer?.off();
|
||||||
|
|
||||||
/* @todo this.courseFormatDelegate.openCourse(course, params).then((response) => {
|
CoreCourseFormatDelegate.instance.openCourse(<CoreCourseAnyCourseData> course, params)
|
||||||
deferred.resolve(response);
|
.then(deferred.resolve).catch(deferred.reject);
|
||||||
}).catch((error) => {
|
|
||||||
deferred.reject(error);
|
|
||||||
});*/
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return deferred.promise;
|
return deferred.promise;
|
||||||
|
|
|
@ -0,0 +1,377 @@
|
||||||
|
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { Injectable, Type } from '@angular/core';
|
||||||
|
import { Params } from '@angular/router';
|
||||||
|
|
||||||
|
import { CoreDelegate, CoreDelegateHandler } from '@classes/delegate';
|
||||||
|
import { CoreCourseAnyCourseData } from '@features/courses/services/courses';
|
||||||
|
import { makeSingleton } from '@singletons';
|
||||||
|
import { CoreCourseSection } from './course';
|
||||||
|
import { CoreCourseFormatDefaultHandler } from './handlers/default-format';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface that all course format handlers must implement.
|
||||||
|
*/
|
||||||
|
export interface CoreCourseFormatHandler extends CoreDelegateHandler {
|
||||||
|
/**
|
||||||
|
* Name of the format the handler supports. E.g. 'singleactivity'.
|
||||||
|
*/
|
||||||
|
format: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the title to use in course page. If not defined, course fullname.
|
||||||
|
* This function will be called without sections first, and then call it again when the sections are retrieved.
|
||||||
|
*
|
||||||
|
* @param course The course.
|
||||||
|
* @param sections List of sections.
|
||||||
|
* @return Title.
|
||||||
|
*/
|
||||||
|
getCourseTitle?(course: CoreCourseAnyCourseData, sections?: CoreCourseSection[]): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
canViewAllSections?(course: CoreCourseAnyCourseData): boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the option blocks should be displayed. Defaults to true.
|
||||||
|
*
|
||||||
|
* @param course The course to check.
|
||||||
|
* @return Whether it can display blocks.
|
||||||
|
*/
|
||||||
|
displayBlocks?(course: CoreCourseAnyCourseData): boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the option to enable section/module download should be displayed. Defaults to true.
|
||||||
|
*
|
||||||
|
* @param course The course to check.
|
||||||
|
* @return Whether the option to enable section/module download should be displayed.
|
||||||
|
*/
|
||||||
|
displayEnableDownload?(course: CoreCourseAnyCourseData): boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
displaySectionSelector?(course: CoreCourseAnyCourseData): boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
displayRefresher?(course: CoreCourseAnyCourseData, sections: CoreCourseSection[]): boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a list of sections, get the "current" section that should be displayed first. Defaults to first section.
|
||||||
|
*
|
||||||
|
* @param course The course to get the title.
|
||||||
|
* @param sections List of sections.
|
||||||
|
* @return Promise resolved with current section.
|
||||||
|
*/
|
||||||
|
getCurrentSection?(course: CoreCourseAnyCourseData, sections: CoreCourseSection[]): Promise<CoreCourseSection>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
openCourse?(course: CoreCourseAnyCourseData, params?: Params): Promise<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the Component to use to display the course format instead of using the default one.
|
||||||
|
* Use it if you want to display a format completely different from the default one.
|
||||||
|
* If you want to customize the default format there are several methods to customize parts of it.
|
||||||
|
* It's recommended to return the class of the component, but you can also return an instance of the component.
|
||||||
|
*
|
||||||
|
* @param course The course to render.
|
||||||
|
* @return Promise resolved with component to use, undefined if not found.
|
||||||
|
*/
|
||||||
|
getCourseFormatComponent?(course: CoreCourseAnyCourseData): Promise<Type<unknown> | undefined>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the Component to use to display the course summary inside the default course format.
|
||||||
|
* It's recommended to return the class of the component, but you can also return an instance of the component.
|
||||||
|
*
|
||||||
|
* @param course The course to render.
|
||||||
|
* @return Promise resolved with component to use, undefined if not found.
|
||||||
|
*/
|
||||||
|
getCourseSummaryComponent?(course: CoreCourseAnyCourseData): Promise<Type<unknown> | undefined>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the Component to use to display the section selector inside the default course format.
|
||||||
|
* It's recommended to return the class of the component, but you can also return an instance of the component.
|
||||||
|
*
|
||||||
|
* @param course The course to render.
|
||||||
|
* @return Promise resolved with component to use, undefined if not found.
|
||||||
|
*/
|
||||||
|
getSectionSelectorComponent?(course: CoreCourseAnyCourseData): Promise<Type<unknown> | undefined>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the Component to use to display a single section. This component will only be used if the user is viewing a
|
||||||
|
* single section. If all the sections are displayed at once then it won't be used.
|
||||||
|
* It's recommended to return the class of the component, but you can also return an instance of the component.
|
||||||
|
*
|
||||||
|
* @param course The course to render.
|
||||||
|
* @return Promise resolved with component to use, undefined if not found.
|
||||||
|
*/
|
||||||
|
getSingleSectionComponent?(course: CoreCourseAnyCourseData): Promise<Type<unknown> | undefined>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the Component to use to display all sections in a course.
|
||||||
|
* It's recommended to return the class of the component, but you can also return an instance of the component.
|
||||||
|
*
|
||||||
|
* @param course The course to render.
|
||||||
|
* @return Promise resolved with component to use, undefined if not found.
|
||||||
|
*/
|
||||||
|
getAllSectionsComponent?(course: CoreCourseAnyCourseData): Promise<Type<unknown> | undefined>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
invalidateData?(course: CoreCourseAnyCourseData, sections: CoreCourseSection[]): Promise<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
shouldRefreshWhenCompletionChanges?(course: CoreCourseAnyCourseData): Promise<boolean>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service to interact with course formats.
|
||||||
|
*/
|
||||||
|
@Injectable({ providedIn: 'root' })
|
||||||
|
export class CoreCourseFormatDelegateService extends CoreDelegate<CoreCourseFormatHandler> {
|
||||||
|
|
||||||
|
protected featurePrefix = 'CoreCourseFormatDelegate_';
|
||||||
|
protected handlerNameProperty = 'format';
|
||||||
|
|
||||||
|
constructor(protected defaultHandler: CoreCourseFormatDefaultHandler) {
|
||||||
|
super('CoreCoursesCourseFormatDelegate', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether it allows seeing all sections at the same time. Defaults to true.
|
||||||
|
*
|
||||||
|
* @param course The course to check.
|
||||||
|
* @return Whether it allows seeing all sections at the same time.
|
||||||
|
*/
|
||||||
|
canViewAllSections(course: CoreCourseAnyCourseData): boolean {
|
||||||
|
return !!this.executeFunctionOnEnabled<boolean>(course.format || '', 'canViewAllSections', [course]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the option blocks should be displayed. Defaults to true.
|
||||||
|
*
|
||||||
|
* @param course The course to check.
|
||||||
|
* @return Whether it can display blocks.
|
||||||
|
*/
|
||||||
|
displayBlocks(course: CoreCourseAnyCourseData): boolean {
|
||||||
|
return !!this.executeFunctionOnEnabled<boolean>(course.format || '', 'displayBlocks', [course]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the option to enable section/module download should be displayed. Defaults to true.
|
||||||
|
*
|
||||||
|
* @param course The course to check.
|
||||||
|
* @return Whether the option to enable section/module download should be displayed
|
||||||
|
*/
|
||||||
|
displayEnableDownload(course: CoreCourseAnyCourseData): boolean {
|
||||||
|
return !!this.executeFunctionOnEnabled<boolean>(course.format || '', 'displayEnableDownload', [course]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
displayRefresher(course: CoreCourseAnyCourseData, sections: CoreCourseSection[]): boolean {
|
||||||
|
return !!this.executeFunctionOnEnabled<boolean>(course.format || '', 'displayRefresher', [course, sections]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the default section selector should be displayed. Defaults to true.
|
||||||
|
*
|
||||||
|
* @param course The course to check.
|
||||||
|
* @return Whether the section selector should be displayed.
|
||||||
|
*/
|
||||||
|
displaySectionSelector(course: CoreCourseAnyCourseData): boolean {
|
||||||
|
return !!this.executeFunctionOnEnabled<boolean>(course.format || '', 'displaySectionSelector', [course]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the component to use to display all sections in a course.
|
||||||
|
*
|
||||||
|
* @param course The course to render.
|
||||||
|
* @return Promise resolved with component to use, undefined if not found.
|
||||||
|
*/
|
||||||
|
async getAllSectionsComponent(course: CoreCourseAnyCourseData): Promise<Type<unknown> | undefined> {
|
||||||
|
try {
|
||||||
|
return await this.executeFunctionOnEnabled<Type<unknown>>(course.format || '', 'getAllSectionsComponent', [course]);
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error('Error getting all sections component', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the component to use to display a course format.
|
||||||
|
*
|
||||||
|
* @param course The course to render.
|
||||||
|
* @return Promise resolved with component to use, undefined if not found.
|
||||||
|
*/
|
||||||
|
async getCourseFormatComponent(course: CoreCourseAnyCourseData): Promise<Type<unknown> | undefined> {
|
||||||
|
try {
|
||||||
|
return await this.executeFunctionOnEnabled<Type<unknown>>(course.format || '', 'getCourseFormatComponent', [course]);
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error('Error getting course format component', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the component to use to display the course summary in the default course format.
|
||||||
|
*
|
||||||
|
* @param course The course to render.
|
||||||
|
* @return Promise resolved with component to use, undefined if not found.
|
||||||
|
*/
|
||||||
|
async getCourseSummaryComponent(course: CoreCourseAnyCourseData): Promise<Type<unknown> | undefined> {
|
||||||
|
try {
|
||||||
|
return await this.executeFunctionOnEnabled<Type<unknown>>(course.format || '', 'getCourseSummaryComponent', [course]);
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error('Error getting course summary component', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a course, return the title to use in the course page.
|
||||||
|
*
|
||||||
|
* @param course The course to get the title.
|
||||||
|
* @param sections List of sections.
|
||||||
|
* @return Course title.
|
||||||
|
*/
|
||||||
|
getCourseTitle(course: CoreCourseAnyCourseData, sections?: CoreCourseSection[]): string | undefined {
|
||||||
|
return this.executeFunctionOnEnabled(course.format || '', 'getCourseTitle', [course, sections]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a course and a list of sections, return the current section that should be displayed first.
|
||||||
|
*
|
||||||
|
* @param course The course to get the title.
|
||||||
|
* @param sections List of sections.
|
||||||
|
* @return Promise resolved with current section.
|
||||||
|
*/
|
||||||
|
async getCurrentSection(course: CoreCourseAnyCourseData, sections: CoreCourseSection[]): Promise<CoreCourseSection> {
|
||||||
|
try {
|
||||||
|
const section = await this.executeFunctionOnEnabled<CoreCourseSection>(
|
||||||
|
course.format || '',
|
||||||
|
'getCurrentSection',
|
||||||
|
[course, sections],
|
||||||
|
);
|
||||||
|
|
||||||
|
return section || sections[0];
|
||||||
|
} catch {
|
||||||
|
// This function should never fail. Just return the first section (usually, "All sections").
|
||||||
|
return sections[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the component to use to display the section selector inside the default course format.
|
||||||
|
*
|
||||||
|
* @param course The course to render.
|
||||||
|
* @return Promise resolved with component to use, undefined if not found.
|
||||||
|
*/
|
||||||
|
async getSectionSelectorComponent(course: CoreCourseAnyCourseData): Promise<Type<unknown> | undefined> {
|
||||||
|
try {
|
||||||
|
return await this.executeFunctionOnEnabled<Type<unknown>>(course.format || '', 'getSectionSelectorComponent', [course]);
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error('Error getting section selector component', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the component to use to display a single section. This component will only be used if the user is viewing
|
||||||
|
* a single section. If all the sections are displayed at once then it won't be used.
|
||||||
|
*
|
||||||
|
* @param course The course to render.
|
||||||
|
* @return Promise resolved with component to use, undefined if not found.
|
||||||
|
*/
|
||||||
|
async getSingleSectionComponent(course: CoreCourseAnyCourseData): Promise<Type<unknown> | undefined> {
|
||||||
|
try {
|
||||||
|
return await this.executeFunctionOnEnabled<Type<unknown>>(course.format || '', 'getSingleSectionComponent', [course]);
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error('Error getting single section component', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
async invalidateData(course: CoreCourseAnyCourseData, sections: CoreCourseSection[]): Promise<void> {
|
||||||
|
await this.executeFunctionOnEnabled(course.format || '', 'invalidateData', [course, sections]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
* @return Promise resolved when done.
|
||||||
|
*/
|
||||||
|
async openCourse(course: CoreCourseAnyCourseData, params?: Params): Promise<void> {
|
||||||
|
await this.executeFunctionOnEnabled(course.format || '', 'openCourse', [course, params]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
async shouldRefreshWhenCompletionChanges(course: CoreCourseAnyCourseData): Promise<boolean | undefined> {
|
||||||
|
return await this.executeFunctionOnEnabled(course.format || '', 'shouldRefreshWhenCompletionChanges', [course]);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CoreCourseFormatDelegate extends makeSingleton(CoreCourseFormatDelegateService) {}
|
|
@ -0,0 +1,193 @@
|
||||||
|
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { Params } from '@angular/router';
|
||||||
|
|
||||||
|
import { CoreCourseAnyCourseData, CoreCourses } from '@features/courses/services/courses';
|
||||||
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
|
import { CoreCourseSection } from '../course';
|
||||||
|
import { CoreCourseFormatHandler } from '../format-delegate';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default handler used when the course format doesn't have a specific implementation.
|
||||||
|
*/
|
||||||
|
@Injectable({ providedIn: 'root' })
|
||||||
|
export class CoreCourseFormatDefaultHandler implements CoreCourseFormatHandler {
|
||||||
|
|
||||||
|
name = 'CoreCourseFormatDefault';
|
||||||
|
format = 'default';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not the handler is enabled on a site level.
|
||||||
|
*
|
||||||
|
* @return Promise resolved with true if enabled.
|
||||||
|
*/
|
||||||
|
async isEnabled(): Promise<boolean> {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the title to use in course page.
|
||||||
|
*
|
||||||
|
* @param course The course.
|
||||||
|
* @return Title.
|
||||||
|
*/
|
||||||
|
getCourseTitle(course: CoreCourseAnyCourseData): string {
|
||||||
|
if (course.displayname) {
|
||||||
|
return course.displayname;
|
||||||
|
} else if (course.fullname) {
|
||||||
|
return course.fullname;
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
canViewAllSections(course: CoreCourseAnyCourseData): 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.
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
displayBlocks(course: CoreCourseAnyCourseData): boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the option to enable section/module download should be displayed. Defaults to true.
|
||||||
|
*
|
||||||
|
* @param course The course to check.
|
||||||
|
* @return Whether the option to enable section/module download should be displayed
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
displayEnableDownload(course: CoreCourseAnyCourseData): 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.
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
displaySectionSelector(course: CoreCourseAnyCourseData): 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.
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
displayRefresher?(course: CoreCourseAnyCourseData, sections: CoreCourseSection[]): 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).
|
||||||
|
*/
|
||||||
|
async getCurrentSection(course: CoreCourseAnyCourseData, sections: CoreCourseSection[]): Promise<CoreCourseSection> {
|
||||||
|
let marker: number | undefined;
|
||||||
|
|
||||||
|
// We need the "marker" to determine the current section.
|
||||||
|
if ('marker' in course) {
|
||||||
|
// We already have it.
|
||||||
|
marker = course.marker;
|
||||||
|
} else if (!CoreCourses.instance.isGetCoursesByFieldAvailable()) {
|
||||||
|
// Cannot get the current section, return all of them.
|
||||||
|
return sections[0];
|
||||||
|
} else {
|
||||||
|
// Try to retrieve the marker.
|
||||||
|
const courseData = await CoreUtils.instance.ignoreErrors(CoreCourses.instance.getCourseByField('id', course.id));
|
||||||
|
|
||||||
|
marker = courseData?.marker;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (marker && marker > 0) {
|
||||||
|
// Find the marked section.
|
||||||
|
const section = sections.find((sect) => sect.section == marker);
|
||||||
|
|
||||||
|
if (section) {
|
||||||
|
return section;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marked section not found or we couldn't retrieve the marker. Return all sections.
|
||||||
|
return sections[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
async invalidateData(course: CoreCourseAnyCourseData, sections: CoreCourseSection[]): Promise<void> {
|
||||||
|
await CoreCourses.instance.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.
|
||||||
|
*/
|
||||||
|
async openCourse(course: CoreCourseAnyCourseData, params?: Params): Promise<void> {
|
||||||
|
params = params || {};
|
||||||
|
Object.assign(params, { course: course });
|
||||||
|
|
||||||
|
// Don't return the .push promise, we don't want to display a loading modal during the page transition.
|
||||||
|
// @todo navCtrl.push('CoreCourseSectionPage', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
async shouldRefreshWhenCompletionChanges(course: CoreCourseAnyCourseData): Promise<boolean> {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -13,6 +13,7 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { Component, Input, OnInit } from '@angular/core';
|
import { Component, Input, OnInit } from '@angular/core';
|
||||||
|
import { CoreCourseHelper } from '@features/course/services/course-helper';
|
||||||
import { NavController } from '@ionic/angular';
|
import { NavController } from '@ionic/angular';
|
||||||
import { CoreCourses, CoreCourseSearchedData } from '../../services/courses';
|
import { CoreCourses, CoreCourseSearchedData } from '../../services/courses';
|
||||||
import { CoreCoursesHelper, CoreCourseWithImageAndColor } from '../../services/courses-helper';
|
import { CoreCoursesHelper, CoreCourseWithImageAndColor } from '../../services/courses-helper';
|
||||||
|
@ -95,13 +96,11 @@ export class CoreCoursesCourseListItemComponent implements OnInit {
|
||||||
* @param course The course to open.
|
* @param course The course to open.
|
||||||
*/
|
*/
|
||||||
openCourse(): void {
|
openCourse(): void {
|
||||||
/* if (this.isEnrolled) {
|
if (this.isEnrolled) {
|
||||||
CoreCourseHelper.instance.openCourse(this.course);
|
CoreCourseHelper.instance.openCourse(this.course);
|
||||||
} else {
|
} else {
|
||||||
this.navCtrl.navigateForward('/main/home/courses/preview', { queryParams: { course: this.course } });
|
this.navCtrl.navigateForward('/main/home/courses/preview', { queryParams: { course: this.course } });
|
||||||
} */
|
}
|
||||||
// @todo while opencourse function is not completed, open preview page.
|
|
||||||
this.navCtrl.navigateForward('/main/home/courses/preview', { queryParams: { course: this.course } });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1588,3 +1588,5 @@ type CoreCourseSetFavouriteCoursesWSParams = {
|
||||||
favourite: boolean; // Favourite status.
|
favourite: boolean; // Favourite status.
|
||||||
}[];
|
}[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type CoreCourseAnyCourseData = CoreEnrolledCourseData | CoreCourseSearchedData | CoreCourseGetCoursesData;
|
||||||
|
|
Loading…
Reference in New Issue