MOBILE-3659 course: Implement module delegate
This commit is contained in:
		
							parent
							
								
									c617ec2dce
								
							
						
					
					
						commit
						8d752d2bf5
					
				| @ -15,7 +15,13 @@ | ||||
| import { Injectable } from '@angular/core'; | ||||
| import { Params } from '@angular/router'; | ||||
| import { CoreSites } from '@services/sites'; | ||||
| import { CoreCourse, CoreCourseSection } from './course'; | ||||
| import { | ||||
|     CoreCourse, | ||||
|     CoreCourseCompletionActivityStatus, | ||||
|     CoreCourseModuleCompletionData, | ||||
|     CoreCourseModuleData, | ||||
|     CoreCourseSection, | ||||
| } from './course'; | ||||
| import { CoreConstants } from '@/core/constants'; | ||||
| import { CoreLogger } from '@singletons/logger'; | ||||
| import { makeSingleton, Translate } from '@singletons'; | ||||
| @ -36,6 +42,7 @@ import { | ||||
|     CoreCourseOptionsHandlerToDisplay, | ||||
|     CoreCourseOptionsMenuHandlerToDisplay, | ||||
| } from './course-options-delegate'; | ||||
| import { CoreCourseModuleDelegate, CoreCourseModuleHandlerData } from './module-delegate'; | ||||
| 
 | ||||
| /** | ||||
|  * Prefetch info of a module. | ||||
| @ -131,11 +138,11 @@ export class CoreCourseHelperProvider { | ||||
|      * @return Whether the sections have content. | ||||
|      */ | ||||
|     addHandlerDataForModules( | ||||
|         sections: CoreCourseSection[], | ||||
|         sections: CoreCourseSectionFormatted[], | ||||
|         courseId: number, | ||||
|         completionStatus?: any, // eslint-disable-line @typescript-eslint/no-unused-vars
 | ||||
|         courseName?: string, // eslint-disable-line @typescript-eslint/no-unused-vars
 | ||||
|         forCoursePage = false, // eslint-disable-line @typescript-eslint/no-unused-vars
 | ||||
|         completionStatus?: Record<string, CoreCourseCompletionActivityStatus>, | ||||
|         courseName?: string, | ||||
|         forCoursePage = false, | ||||
|     ): boolean { | ||||
| 
 | ||||
|         let hasContent = false; | ||||
| @ -147,32 +154,39 @@ export class CoreCourseHelperProvider { | ||||
| 
 | ||||
|             hasContent = true; | ||||
| 
 | ||||
|             /* @todo | ||||
|             section.modules.forEach((module) => { | ||||
|                 module.handlerData = this.moduleDelegate.getModuleDataFor(module.modname, module, courseId, section.id, | ||||
|                         forCoursePage); | ||||
|                 module.handlerData = CoreCourseModuleDelegate.instance.getModuleDataFor( | ||||
|                     module.modname, | ||||
|                     module, | ||||
|                     courseId, | ||||
|                     section.id, | ||||
|                     forCoursePage, | ||||
|                 ); | ||||
| 
 | ||||
|                 if (module.completiondata && module.completion > 0) { | ||||
|                 if (module.completiondata && module.completion && module.completion > 0) { | ||||
|                     module.completiondata.courseId = courseId; | ||||
|                     module.completiondata.courseName = courseName; | ||||
|                     module.completiondata.tracking = module.completion; | ||||
|                     module.completiondata.cmid = module.id; | ||||
| 
 | ||||
|                     // Use of completionstatus is deprecated, use completiondata instead.
 | ||||
|                     module.completionstatus = module.completiondata; | ||||
|                 } else if (completionStatus && typeof completionStatus[module.id] != 'undefined') { | ||||
|                     // Should not happen on > 3.6. Check if activity has completions and if it's marked.
 | ||||
|                     module.completiondata = completionStatus[module.id]; | ||||
|                     module.completiondata.courseId = courseId; | ||||
|                     module.completiondata.courseName = courseName; | ||||
|                     const activityStatus = completionStatus[module.id]; | ||||
| 
 | ||||
|                     // Use of completionstatus is deprecated, use completiondata instead.
 | ||||
|                     module.completionstatus = module.completiondata; | ||||
|                     module.completiondata = { | ||||
|                         state: activityStatus.state, | ||||
|                         timecompleted: activityStatus.timecompleted, | ||||
|                         overrideby: activityStatus.overrideby || 0, | ||||
|                         valueused: activityStatus.valueused, | ||||
|                         tracking: activityStatus.tracking, | ||||
|                         courseId, | ||||
|                         courseName, | ||||
|                         cmid: module.id, | ||||
|                     }; | ||||
|                 } | ||||
| 
 | ||||
|                 // Check if the module is stealth.
 | ||||
|                 module.isStealth = module.visibleoncoursepage === 0 || (module.visible && !section.visible); | ||||
|             });*/ | ||||
|                 module.isStealth = module.visibleoncoursepage === 0 || (!!module.visible && !section.visible); | ||||
|             }); | ||||
|         }); | ||||
| 
 | ||||
|         return hasContent; | ||||
| @ -295,7 +309,7 @@ export class CoreCourseHelperProvider { | ||||
|      * @return Promise resolved when done. | ||||
|      * @todo module type. | ||||
|      */ | ||||
|     async confirmAndRemoveFiles(module: any, courseId: number, done?: () => void): Promise<void> { | ||||
|     async confirmAndRemoveFiles(module: CoreCourseModuleData, courseId: number, done?: () => void): Promise<void> { | ||||
|         let modal: CoreIonLoadingElement | undefined; | ||||
| 
 | ||||
|         try { | ||||
| @ -724,7 +738,7 @@ export class CoreCourseHelperProvider { | ||||
|      * @return Promise resolved with the module's course ID. | ||||
|      * @todo module type. | ||||
|      */ | ||||
|     async getModuleCourseIdByInstance(id: number, module: any, siteId?: string): Promise<number> { | ||||
|     async getModuleCourseIdByInstance(id: number, module: string, siteId?: string): Promise<number> { | ||||
|         try { | ||||
|             const cm = await CoreCourse.instance.getModuleBasicInfoByInstance(id, module, siteId); | ||||
| 
 | ||||
| @ -1017,7 +1031,7 @@ export class CoreCourseHelperProvider { | ||||
|      */ | ||||
|     // @todo remove when done.
 | ||||
|     // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | ||||
|     async removeModuleStoredData(module: any, courseId: number): Promise<void> { | ||||
|     async removeModuleStoredData(module: CoreCourseModuleData, courseId: number): Promise<void> { | ||||
|         const promises: Promise<void>[] = []; | ||||
| 
 | ||||
|         // @todo
 | ||||
| @ -1035,3 +1049,29 @@ export class CoreCourseHelperProvider { | ||||
| } | ||||
| 
 | ||||
| export class CoreCourseHelper extends makeSingleton(CoreCourseHelperProvider) {} | ||||
| 
 | ||||
| /** | ||||
|  * Section with calculated data. | ||||
|  */ | ||||
| export type CoreCourseSectionFormatted = Omit<CoreCourseSection, 'modules'> & { | ||||
|     modules: CoreCourseModuleDataFormatted[]; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Module with calculated data. | ||||
|  */ | ||||
| export type CoreCourseModuleDataFormatted = Omit<CoreCourseModuleData, 'completiondata'> & { | ||||
|     isStealth?: boolean; | ||||
|     handlerData?: CoreCourseModuleHandlerData; | ||||
|     completiondata?: CoreCourseModuleCompletionDataFormatted; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Module completion with calculated data. | ||||
|  */ | ||||
| export type CoreCourseModuleCompletionDataFormatted = CoreCourseModuleCompletionData & { | ||||
|     courseId?: number; | ||||
|     courseName?: string; | ||||
|     tracking?: number; | ||||
|     cmid?: number; | ||||
| }; | ||||
|  | ||||
| @ -31,12 +31,13 @@ import { CoreCourseOffline } from './course-offline'; | ||||
| import { CoreError } from '@classes/errors/error'; | ||||
| import { | ||||
|     CoreCourseAnyCourseData, | ||||
|     CoreCoursesMyCoursesUpdatedEventData, | ||||
|     CoreCoursesProvider, | ||||
| } from '../../courses/services/courses'; | ||||
| import { CoreDomUtils } from '@services/utils/dom'; | ||||
| import { CoreWSError } from '@classes/errors/wserror'; | ||||
| import { CorePushNotifications } from '@features/pushnotifications/services/pushnotifications'; | ||||
| import { CoreCourseHelper } from './course-helper'; | ||||
| import { CoreCourseHelper, CoreCourseModuleCompletionDataFormatted } from './course-helper'; | ||||
| import { CoreCourseFormatDelegate } from './format-delegate'; | ||||
| 
 | ||||
| const ROOT_CACHE_KEY = 'mmCourse:'; | ||||
| @ -110,7 +111,7 @@ export class CoreCourseProvider { | ||||
|      * @param completion Completion status of the module. | ||||
|      * @todo Add completion type. | ||||
|      */ | ||||
|     checkModuleCompletion(courseId: number, completion: any): void { | ||||
|     checkModuleCompletion(courseId: number, completion: CoreCourseModuleCompletionDataFormatted): void { | ||||
|         if (completion && completion.tracking === 2 && completion.state === 0) { | ||||
|             this.invalidateSections(courseId).finally(() => { | ||||
|                 CoreEvents.trigger(CoreEvents.COMPLETION_MODULE_VIEWED, { courseId: courseId }); | ||||
| @ -874,7 +875,7 @@ export class CoreCourseProvider { | ||||
|         if (!response.status) { | ||||
|             throw Error('WS core_course_view_course failed.'); | ||||
|         } else { | ||||
|             CoreEvents.trigger(CoreCoursesProvider.EVENT_MY_COURSES_UPDATED, { | ||||
|             CoreEvents.trigger<CoreCoursesMyCoursesUpdatedEventData>(CoreCoursesProvider.EVENT_MY_COURSES_UPDATED, { | ||||
|                 courseId: courseId, | ||||
|                 action: CoreCoursesProvider.ACTION_VIEW, | ||||
|             }, site.getId()); | ||||
| @ -1352,7 +1353,7 @@ export type CoreCourseGetCourseModuleWSResponse = { | ||||
| /** | ||||
|  * Course module type. | ||||
|  */ | ||||
| export type CoreCourseModuleData = { // List of module.
 | ||||
| export type CoreCourseModuleData = { | ||||
|     id: number; // Activity id.
 | ||||
|     course?: number; // The course id.
 | ||||
|     url?: string; // Activity url.
 | ||||
| @ -1374,12 +1375,7 @@ export type CoreCourseModuleData = { // List of module. | ||||
|     customdata?: string; // Custom data (JSON encoded).
 | ||||
|     noviewlink?: boolean; // Whether the module has no view page.
 | ||||
|     completion?: number; // Type of completion tracking: 0 means none, 1 manual, 2 automatic.
 | ||||
|     completiondata?: { // Module completion data.
 | ||||
|         state: number; // Completion state value: 0 means incomplete, 1 complete, 2 complete pass, 3 complete fail.
 | ||||
|         timecompleted: number; // Timestamp for completion status.
 | ||||
|         overrideby: number; // The user id who has overriden the status.
 | ||||
|         valueused?: boolean; // Whether the completion status affects the availability of another activity.
 | ||||
|     }; | ||||
|     completiondata?: CoreCourseModuleCompletionData; // Module completion data.
 | ||||
|     contents: CoreCourseModuleContentFile[]; | ||||
|     contentsinfo?: { // Contents summary information.
 | ||||
|         filescount: number; // Total number of files.
 | ||||
| @ -1390,6 +1386,16 @@ export type CoreCourseModuleData = { // List of module. | ||||
|     }; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Module completion data. | ||||
|  */ | ||||
| export type CoreCourseModuleCompletionData = { | ||||
|     state: number; // Completion state value: 0 means incomplete, 1 complete, 2 complete pass, 3 complete fail.
 | ||||
|     timecompleted: number; // Timestamp for completion status.
 | ||||
|     overrideby: number; // The user id who has overriden the status.
 | ||||
|     valueused?: boolean; // Whether the completion status affects the availability of another activity.
 | ||||
| }; | ||||
| 
 | ||||
| export type CoreCourseModuleContentFile = { | ||||
|     type: string; // A file or a folder or external link.
 | ||||
|     filename: string; // Filename.
 | ||||
|  | ||||
							
								
								
									
										111
									
								
								src/core/features/course/services/handlers/default-module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								src/core/features/course/services/handlers/default-module.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,111 @@ | ||||
| // (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 { NavigationOptions } from '@ionic/angular/providers/nav-controller'; | ||||
| 
 | ||||
| import { CoreSites } from '@services/sites'; | ||||
| import { CoreCourseModuleHandler, CoreCourseModuleHandlerData } from '../module-delegate'; | ||||
| import { CoreCourse, CoreCourseModuleBasicInfo, CoreCourseModuleData } from '../course'; | ||||
| import { CoreCourseAnyCourseData } from '@features/courses/services/courses'; | ||||
| 
 | ||||
| /** | ||||
|  * Default handler used when the module doesn't have a specific implementation. | ||||
|  */ | ||||
| @Injectable({ providedIn: 'root' }) | ||||
| export class CoreCourseModuleDefaultHandler implements CoreCourseModuleHandler { | ||||
| 
 | ||||
|     name = 'CoreCourseModuleDefault'; | ||||
|     modName = 'default'; | ||||
| 
 | ||||
|     /** | ||||
|      * Whether or not the handler is enabled on a site level. | ||||
|      * | ||||
|      * @return True or promise resolved with true if enabled. | ||||
|      */ | ||||
|     async isEnabled(): Promise<boolean> { | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the data required to display the module in the course contents view. | ||||
|      * | ||||
|      * @param module The module object. | ||||
|      * @param courseId The course ID. | ||||
|      * @param sectionId The section ID. | ||||
|      * @return Data to render the module. | ||||
|      */ | ||||
|     getData( | ||||
|         module: CoreCourseModuleData | CoreCourseModuleBasicInfo, | ||||
|         courseId: number, // eslint-disable-line @typescript-eslint/no-unused-vars
 | ||||
|         sectionId: number, // eslint-disable-line @typescript-eslint/no-unused-vars
 | ||||
|         forCoursePage: boolean, // eslint-disable-line @typescript-eslint/no-unused-vars
 | ||||
|     ): CoreCourseModuleHandlerData { | ||||
|         // Return the default data.
 | ||||
|         const defaultData: CoreCourseModuleHandlerData = { | ||||
|             icon: CoreCourse.instance.getModuleIconSrc(module.modname, 'modicon' in module ? module.modicon : undefined), | ||||
|             title: module.name, | ||||
|             class: 'core-course-default-handler core-course-module-' + module.modname + '-handler', | ||||
|             // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | ||||
|             action: (event: Event, module: CoreCourseModuleData, courseId: number, options?: NavigationOptions): void => { | ||||
|                 event.preventDefault(); | ||||
|                 event.stopPropagation(); | ||||
| 
 | ||||
|                 // @todo navCtrl.push('CoreCourseUnsupportedModulePage', { module: module, courseId: courseId }, options);
 | ||||
|             }, | ||||
|         }; | ||||
| 
 | ||||
|         if ('url' in module && module.url) { | ||||
|             defaultData.buttons = [{ | ||||
|                 icon: 'open', | ||||
|                 label: 'core.openinbrowser', | ||||
|                 action: (e: Event): void => { | ||||
|                     e.preventDefault(); | ||||
|                     e.stopPropagation(); | ||||
| 
 | ||||
|                     CoreSites.instance.getCurrentSite()!.openInBrowserWithAutoLoginIfSameSite(module.url!); | ||||
|                 }, | ||||
|             }]; | ||||
|         } | ||||
| 
 | ||||
|         return defaultData; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the component to render the module. This is needed to support singleactivity course format. | ||||
|      * The component returned must implement CoreCourseModuleMainComponent. | ||||
|      * It's recommended to return the class of the component, but you can also return an instance of the component. | ||||
|      * | ||||
|      * @param course The course object. | ||||
|      * @param module The module object. | ||||
|      * @return The component (or promise resolved with component) to use, undefined if not found. | ||||
|      */ | ||||
|     // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | ||||
|     async getMainComponent(course: CoreCourseAnyCourseData, module: CoreCourseModuleData): Promise<Type<unknown> | undefined> { | ||||
|         // We can't inject CoreCourseUnsupportedModuleComponent here due to circular dependencies.
 | ||||
|         // Don't return anything, by default it will use CoreCourseUnsupportedModuleComponent.
 | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Whether to display the course refresher in single activity course format. If it returns false, a refresher must be | ||||
|      * included in the template that calls the doRefresh method of the component. Defaults to true. | ||||
|      * | ||||
|      * @return Whether the refresher should be displayed. | ||||
|      */ | ||||
|     displayRefresherInSingleActivity(): boolean { | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										369
									
								
								src/core/features/course/services/module-delegate.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										369
									
								
								src/core/features/course/services/module-delegate.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,369 @@ | ||||
| // (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 { SafeUrl } from '@angular/platform-browser'; | ||||
| import { Params } from '@angular/router'; | ||||
| import { IonRefresher } from '@ionic/angular'; | ||||
| 
 | ||||
| import { CoreSite } from '@classes/site'; | ||||
| import { CoreCourseModuleDefaultHandler } from './handlers/default-module'; | ||||
| import { CoreDelegate, CoreDelegateHandler } from '@classes/delegate'; | ||||
| import { CoreCourseAnyCourseData } from '@features/courses/services/courses'; | ||||
| import { CoreCourse, CoreCourseModuleBasicInfo, CoreCourseModuleData } from './course'; | ||||
| import { CoreSites } from '@services/sites'; | ||||
| import { NavigationOptions } from '@ionic/angular/providers/nav-controller'; | ||||
| import { makeSingleton } from '@singletons'; | ||||
| 
 | ||||
| /** | ||||
|  * Interface that all course module handlers must implement. | ||||
|  */ | ||||
| export interface CoreCourseModuleHandler extends CoreDelegateHandler { | ||||
|     /** | ||||
|      * Name of the module. It should match the "modname" of the module returned in core_course_get_contents. | ||||
|      */ | ||||
|     modName: string; | ||||
| 
 | ||||
|     /** | ||||
|      * List of supported features. The keys should be the name of the feature. | ||||
|      * This is to replicate the "plugin_supports" function of Moodle. | ||||
|      * If you need some dynamic checks please implement the supportsFeature function. | ||||
|      */ | ||||
|     supportedFeatures?: Record<string, unknown>; | ||||
| 
 | ||||
|     /** | ||||
|      * Get the data required to display the module in the course contents view. | ||||
|      * | ||||
|      * @param module The module object. | ||||
|      * @param courseId The course ID. | ||||
|      * @param sectionId The section ID. | ||||
|      * @param forCoursePage Whether the data will be used to render the course page. | ||||
|      * @return Data to render the module. | ||||
|      */ | ||||
|     getData( | ||||
|         module: CoreCourseModuleData | CoreCourseModuleBasicInfo, | ||||
|         courseId: number, | ||||
|         sectionId: number, | ||||
|         forCoursePage: boolean, | ||||
|     ): CoreCourseModuleHandlerData; | ||||
| 
 | ||||
|     /** | ||||
|      * Get the component to render the module. This is needed to support singleactivity course format. | ||||
|      * The component returned must implement CoreCourseModuleMainComponent. | ||||
|      * It's recommended to return the class of the component, but you can also return an instance of the component. | ||||
|      * | ||||
|      * @param course The course object. | ||||
|      * @param module The module object. | ||||
|      * @return Promise resolved with component to use, undefined if not found. | ||||
|      */ | ||||
|     getMainComponent(course: CoreCourseAnyCourseData, module: CoreCourseModuleData): Promise<Type<unknown> | undefined>; | ||||
| 
 | ||||
|     /** | ||||
|      * Whether to display the course refresher in single activity course format. If it returns false, a refresher must be | ||||
|      * included in the template that calls the doRefresh method of the component. Defaults to true. | ||||
|      * | ||||
|      * @return Whether the refresher should be displayed. | ||||
|      */ | ||||
|     displayRefresherInSingleActivity?(): boolean; | ||||
| 
 | ||||
|     /** | ||||
|      * Get the icon src for the module. | ||||
|      * | ||||
|      * @return The icon src. | ||||
|      */ | ||||
|     getIconSrc?(): string; | ||||
| 
 | ||||
|     /** | ||||
|      * Check if this type of module supports a certain feature. | ||||
|      * If this function is implemented, the supportedFeatures object will be ignored. | ||||
|      * | ||||
|      * @param feature The feature to check. | ||||
|      * @return The result of the supports check. | ||||
|      */ | ||||
|     supportsFeature?(feature: string): unknown; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Data needed to render the module in course contents. | ||||
|  */ | ||||
| export interface CoreCourseModuleHandlerData { | ||||
|     /** | ||||
|      * The title to display in the module. | ||||
|      */ | ||||
|     title: string; | ||||
| 
 | ||||
|     /** | ||||
|      * The accessibility title to use in the module. If not provided, title will be used. | ||||
|      */ | ||||
|     a11yTitle?: string; | ||||
| 
 | ||||
|     /** | ||||
|      * The image to use as icon (path to the image). | ||||
|      */ | ||||
|     icon?: string | SafeUrl; | ||||
| 
 | ||||
|     /** | ||||
|      * The class to assign to the item. | ||||
|      */ | ||||
|     class?: string; | ||||
| 
 | ||||
|     /** | ||||
|      * The text to show in an extra badge. | ||||
|      */ | ||||
|     extraBadge?: string; | ||||
| 
 | ||||
|     /** | ||||
|      * The color of the extra badge. Default: primary. | ||||
|      */ | ||||
|     extraBadgeColor?: string; | ||||
| 
 | ||||
|     /** | ||||
|      * Whether to display a button to download/refresh the module if it's downloadable. | ||||
|      * If it's set to true, the app will show a download/refresh button when needed and will handle the download of the | ||||
|      * module using CoreCourseModulePrefetchDelegate. | ||||
|      */ | ||||
|     showDownloadButton?: boolean; | ||||
| 
 | ||||
|     /** | ||||
|      * The buttons to display in the module item. | ||||
|      */ | ||||
|     buttons?: CoreCourseModuleHandlerButton[]; | ||||
| 
 | ||||
|     /** | ||||
|      * Whether to display a spinner where the download button is displayed. The module icon, title, etc. will be displayed. | ||||
|      */ | ||||
|     spinner?: boolean; | ||||
| 
 | ||||
|     /** | ||||
|      * Whether the data is being loaded. If true, it will display a spinner in the whole module, nothing else will be shown. | ||||
|      */ | ||||
|     loading?: boolean; | ||||
| 
 | ||||
|     /** | ||||
|      * Action to perform when the module is clicked. | ||||
|      * | ||||
|      * @param event The click event. | ||||
|      * @param module The module object. | ||||
|      * @param courseId The course ID. | ||||
|      * @param options Options for the navigation. | ||||
|      * @param params Params for the new page. | ||||
|      */ | ||||
|     action?(event: Event, module: CoreCourseModuleData, courseId: number, options?: NavigationOptions, params?: Params): void; | ||||
| 
 | ||||
|     /** | ||||
|      * Updates the status of the module. | ||||
|      * | ||||
|      * @param status Module status. | ||||
|      */ | ||||
|     updateStatus?(status: string): void; | ||||
| 
 | ||||
|     /** | ||||
|      * On Destroy function in case it's needed. | ||||
|      */ | ||||
|     onDestroy?(): void; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Interface that all the components to render the module in singleactivity must implement. | ||||
|  */ | ||||
| export interface CoreCourseModuleMainComponent { | ||||
|     /** | ||||
|      * Refresh the data. | ||||
|      * | ||||
|      * @param refresher Refresher. | ||||
|      * @param done Function to call when done. | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     doRefresh(refresher?: CustomEvent<IonRefresher>, done?: () => void): Promise<void>; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * A button to display in a module item. | ||||
|  */ | ||||
| export interface CoreCourseModuleHandlerButton { | ||||
|     /** | ||||
|      * The label to add to the button. | ||||
|      */ | ||||
|     label: string; | ||||
| 
 | ||||
|     /** | ||||
|      * The name of the button icon. | ||||
|      */ | ||||
|     icon: string; | ||||
| 
 | ||||
|     /** | ||||
|      * Whether the button should be hidden. | ||||
|      */ | ||||
|     hidden?: boolean; | ||||
| 
 | ||||
|     /** | ||||
|      * The name of the button icon to use in iOS instead of "icon". | ||||
|      */ | ||||
|     iosIcon?: string; | ||||
| 
 | ||||
|     /** | ||||
|      * The name of the button icon to use in MaterialDesign instead of "icon". | ||||
|      */ | ||||
|     mdIcon?: string; | ||||
| 
 | ||||
|     /** | ||||
|      * Action to perform when the button is clicked. | ||||
|      * | ||||
|      * @param event The click event. | ||||
|      * @param navCtrl NavController instance. | ||||
|      * @param module The module object. | ||||
|      * @param courseId The course ID. | ||||
|      */ | ||||
|     action(event: Event, module: CoreCourseModuleData, courseId: number): void; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Delegate to register module handlers. | ||||
|  */ | ||||
| @Injectable({ providedIn: 'root' }) | ||||
| export class CoreCourseModuleDelegateService extends CoreDelegate<CoreCourseModuleHandler> { | ||||
| 
 | ||||
|     protected featurePrefix = 'CoreCourseModuleDelegate_'; | ||||
|     protected handlerNameProperty = 'modName'; | ||||
| 
 | ||||
|     constructor(protected defaultHandler: CoreCourseModuleDefaultHandler) { | ||||
|         super('CoreCourseModuleDelegate', true); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the component to render the module. | ||||
|      * | ||||
|      * @param course The course object. | ||||
|      * @param module The module object. | ||||
|      * @return Promise resolved with component to use, undefined if not found. | ||||
|      */ | ||||
|     async getMainComponent(course: CoreCourseAnyCourseData, module: CoreCourseModuleData): Promise<Type<unknown> | undefined> { | ||||
|         try { | ||||
|             return await this.executeFunctionOnEnabled<Type<unknown>>(module.modname, 'getMainComponent', [course, module]); | ||||
|         } catch (error) { | ||||
|             this.logger.error('Error getting main component', error); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the data required to display the module in the course contents view. | ||||
|      * | ||||
|      * @param modname The name of the module type. | ||||
|      * @param module The module object. | ||||
|      * @param courseId The course ID. | ||||
|      * @param sectionId The section ID. | ||||
|      * @param forCoursePage Whether the data will be used to render the course page. | ||||
|      * @return Data to render the module. | ||||
|      */ | ||||
|     getModuleDataFor( | ||||
|         modname: string, | ||||
|         module: CoreCourseModuleData | CoreCourseModuleBasicInfo, | ||||
|         courseId: number, | ||||
|         sectionId: number, | ||||
|         forCoursePage?: boolean, | ||||
|     ): CoreCourseModuleHandlerData | undefined { | ||||
|         return this.executeFunctionOnEnabled<CoreCourseModuleHandlerData>( | ||||
|             modname, | ||||
|             'getData', | ||||
|             [module, courseId, sectionId, forCoursePage], | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Check if a certain module type is disabled in a site. | ||||
|      * | ||||
|      * @param modname The name of the module type. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise resolved with boolean: whether module is disabled. | ||||
|      */ | ||||
|     async isModuleDisabled(modname: string, siteId?: string): Promise<boolean> { | ||||
|         const site = await CoreSites.instance.getSite(siteId); | ||||
| 
 | ||||
|         return this.isModuleDisabledInSite(modname, site); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Check if a certain module type is disabled in a site. | ||||
|      * | ||||
|      * @param modname The name of the module type. | ||||
|      * @param site Site. If not defined, use current site. | ||||
|      * @return Whether module is disabled. | ||||
|      */ | ||||
|     isModuleDisabledInSite(modname: string, site?: CoreSite): boolean { | ||||
|         const handler = this.getHandler(modname, false); | ||||
| 
 | ||||
|         if (handler) { | ||||
|             site = site || CoreSites.instance.getCurrentSite(); | ||||
| 
 | ||||
|             if (!site) { | ||||
|                 return true; | ||||
|             } | ||||
| 
 | ||||
|             return this.isFeatureDisabled(handler, site); | ||||
|         } | ||||
| 
 | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Whether to display the course refresher in single activity course format. If it returns false, a refresher must be | ||||
|      * included in the template that calls the doRefresh method of the component. Defaults to true. | ||||
|      * | ||||
|      * @param modname The name of the module type. | ||||
|      * @return Whether the refresher should be displayed. | ||||
|      */ | ||||
|     displayRefresherInSingleActivity(modname: string): boolean { | ||||
|         return !!this.executeFunctionOnEnabled<boolean>(modname, 'displayRefresherInSingleActivity'); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the icon src for a certain type of module. | ||||
|      * | ||||
|      * @param modname The name of the module type. | ||||
|      * @param modicon The mod icon string. | ||||
|      * @return The icon src. | ||||
|      */ | ||||
|     getModuleIconSrc(modname: string, modicon?: string): string { | ||||
|         return this.executeFunctionOnEnabled<string>(modname, 'getIconSrc') || | ||||
|             CoreCourse.instance.getModuleIconSrc(modname, modicon); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Check if a certain type of module supports a certain feature. | ||||
|      * | ||||
|      * @param modname The modname. | ||||
|      * @param feature The feature to check. | ||||
|      * @param defaultValue Value to return if the module is not supported or doesn't know if it's supported. | ||||
|      * @return The result of the supports check. | ||||
|      */ | ||||
|     supportsFeature<T = unknown>(modname: string, feature: string, defaultValue: T): T { | ||||
|         const handler = this.enabledHandlers[modname]; | ||||
|         let result: T | undefined; | ||||
| 
 | ||||
|         if (handler) { | ||||
|             if (handler.supportsFeature) { | ||||
|                 // The handler specified a function to determine the feature, use it.
 | ||||
|                 result = <T> handler.supportsFeature(feature); | ||||
|             } else if (handler.supportedFeatures) { | ||||
|                 // Handler has an object to determine the feature, use it.
 | ||||
|                 result = <T> handler.supportedFeatures[feature]; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return result ?? defaultValue; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| export class CoreCourseModuleDelegate extends makeSingleton(CoreCourseModuleDelegateService) {} | ||||
| @ -1174,7 +1174,7 @@ export class CoreCourses extends makeSingleton(CoreCoursesProvider) {} | ||||
| export type CoreCoursesMyCoursesUpdatedEventData = { | ||||
|     action: string; // Action performed.
 | ||||
|     courseId?: number; // Course ID affected (if any).
 | ||||
|     course?: any; // Course affected (if any).
 | ||||
|     course?: CoreCourseAnyCourseData; // Course affected (if any).
 | ||||
|     state?: string; // Only for ACTION_STATE_CHANGED. The state that changed (hidden, favourite).
 | ||||
|     value?: boolean; // The new value for the state changed.
 | ||||
| }; | ||||
|  | ||||
| @ -25,6 +25,7 @@ import { CoreCourses, CoreCoursesProvider } from '@features//courses/services/co | ||||
| import { CoreEventObserver, CoreEvents } from '@singletons/events'; | ||||
| import { CoreCourseHelper } from '@features/course/services/course-helper'; | ||||
| import { CoreBlockCourseBlocksComponent } from '@features/block/components/course-blocks/course-blocks'; | ||||
| import { CoreCourseModuleDelegate, CoreCourseModuleHandlerData } from '@features/course/services/module-delegate'; | ||||
| 
 | ||||
| /** | ||||
|  * Page that displays site home index. | ||||
| @ -51,7 +52,7 @@ export class CoreSiteHomeIndexPage implements OnInit, OnDestroy { | ||||
|     downloadCourseEnabled = false; | ||||
|     downloadCoursesEnabled = false; | ||||
|     downloadEnabledIcon = 'far-square'; | ||||
|     newsForumModule?: CoreCourseModuleBasicInfo; | ||||
|     newsForumModule?: NewsForum; | ||||
| 
 | ||||
|     protected updateSiteObserver?: CoreEventObserver; | ||||
| 
 | ||||
| @ -112,13 +113,13 @@ export class CoreSiteHomeIndexPage implements OnInit, OnDestroy { | ||||
|             try { | ||||
|                 const forum = await CoreSiteHome.instance.getNewsForum(); | ||||
|                 this.newsForumModule = await CoreCourse.instance.getModuleBasicInfo(forum.cmid); | ||||
|                 /* @todo this.newsForumModule.handlerData = this.moduleDelegate.getModuleDataFor( | ||||
|                 this.newsForumModule.handlerData = CoreCourseModuleDelegate.instance.getModuleDataFor( | ||||
|                     this.newsForumModule.modname, | ||||
|                     this.newsForumModule, | ||||
|                     this.siteHomeId, | ||||
|                     this.newsForumModule.section, | ||||
|                     true, | ||||
|                 );*/ | ||||
|                 ); | ||||
|             } catch { | ||||
|                 // Ignore errors.
 | ||||
|             } | ||||
| @ -235,3 +236,7 @@ export class CoreSiteHomeIndexPage implements OnInit, OnDestroy { | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| type NewsForum = CoreCourseModuleBasicInfo & { | ||||
|     handlerData?: CoreCourseModuleHandlerData; | ||||
| }; | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user