MOBILE-4193 core: Consolidate module icons sources
This commit is contained in:
		
							parent
							
								
									2bedc67695
								
							
						
					
					
						commit
						0c998b8f8b
					
				| @ -18,6 +18,7 @@ import { CoreDomUtils } from '@services/utils/dom'; | ||||
| import { CoreCourse } from '@features/course/services/course'; | ||||
| import { CoreSiteWSPreSets } from '@classes/site'; | ||||
| import { makeSingleton } from '@singletons'; | ||||
| import { CoreCourseModuleDelegate } from '@features/course/services/module-delegate'; | ||||
| 
 | ||||
| const ROOT_CACHE_KEY = 'AddonBlockRecentlyAccessedItems:'; | ||||
| 
 | ||||
| @ -54,15 +55,15 @@ export class AddonBlockRecentlyAccessedItemsProvider { | ||||
| 
 | ||||
|         const cmIds: number[] = []; | ||||
| 
 | ||||
|         items = items.map((item) => { | ||||
|         items = await Promise.all(items.map(async (item) => { | ||||
|             const modicon = item.icon && CoreDomUtils.getHTMLElementAttribute(item.icon, 'src'); | ||||
| 
 | ||||
|             item.iconUrl = CoreCourse.getModuleIconSrc(item.modname, modicon || undefined); | ||||
|             item.iconUrl = await CoreCourseModuleDelegate.getModuleIconSrc(item.modname, modicon || undefined); | ||||
|             item.iconTitle = item.icon && CoreDomUtils.getHTMLElementAttribute(item.icon, 'title'); | ||||
|             cmIds.push(item.cmid); | ||||
| 
 | ||||
|             return item; | ||||
|         }); | ||||
|         })); | ||||
| 
 | ||||
|         // Check if the viewed module should be updated for each activity.
 | ||||
|         const lastViewedMap = await CoreCourse.getCertainModulesViewed(cmIds, site.getId()); | ||||
|  | ||||
| @ -15,9 +15,10 @@ | ||||
| import { AddonBlockTimeline } from '@addons/block/timeline/services/timeline'; | ||||
| import { AddonCalendarEvent } from '@addons/calendar/services/calendar'; | ||||
| import { CoreCourse } from '@features/course/services/course'; | ||||
| import { CoreCourseModuleDelegate } from '@features/course/services/module-delegate'; | ||||
| import { CoreEnrolledCourseDataWithOptions } from '@features/courses/services/courses-helper'; | ||||
| import { CoreTimeUtils } from '@services/utils/time'; | ||||
| import { BehaviorSubject } from 'rxjs'; | ||||
| import { BehaviorSubject, Observable } from 'rxjs'; | ||||
| 
 | ||||
| /** | ||||
|  * A collection of events displayed in the timeline block. | ||||
| @ -28,12 +29,8 @@ export class AddonBlockTimelineSection { | ||||
|     overdue: boolean; | ||||
|     dateRange: AddonBlockTimelineDateRange; | ||||
|     course?: CoreEnrolledCourseDataWithOptions; | ||||
|     data$: BehaviorSubject<{ | ||||
|         events: AddonBlockTimelineDayEvents[]; | ||||
|         lastEventId?: number; | ||||
|         canLoadMore: boolean; | ||||
|         loadingMore: boolean; | ||||
|     }>; | ||||
| 
 | ||||
|     private dataSubject$: BehaviorSubject<AddonBlockTimelineSectionData>; | ||||
| 
 | ||||
|     constructor( | ||||
|         search: string | null, | ||||
| @ -47,30 +44,42 @@ export class AddonBlockTimelineSection { | ||||
|         this.overdue = overdue; | ||||
|         this.dateRange = dateRange; | ||||
|         this.course = course; | ||||
|         this.data$ = new BehaviorSubject({ | ||||
|             events: courseEvents ? this.reduceEvents(courseEvents, overdue, dateRange) : [], | ||||
|         this.dataSubject$ = new BehaviorSubject({ | ||||
|             events: [], | ||||
|             lastEventId: canLoadMore, | ||||
|             canLoadMore: typeof canLoadMore !== 'undefined', | ||||
|             loadingMore: false, | ||||
|         }); | ||||
| 
 | ||||
|         if (courseEvents) { | ||||
|             // eslint-disable-next-line promise/catch-or-return
 | ||||
|             this.reduceEvents(courseEvents, overdue, dateRange).then(events => this.dataSubject$.next({ | ||||
|                 ...this.dataSubject$.value, | ||||
|                 events, | ||||
|             })); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     get data$(): Observable<AddonBlockTimelineSectionData> { | ||||
|         return this.dataSubject$; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Load more events. | ||||
|      */ | ||||
|     async loadMore(): Promise<void> { | ||||
|         this.data$.next({ | ||||
|             ...this.data$.value, | ||||
|         this.dataSubject$.next({ | ||||
|             ...this.dataSubject$.value, | ||||
|             loadingMore: true, | ||||
|         }); | ||||
| 
 | ||||
|         const lastEventId = this.data$.value.lastEventId; | ||||
|         const lastEventId = this.dataSubject$.value.lastEventId; | ||||
|         const { events, canLoadMore } = this.course | ||||
|             ? await AddonBlockTimeline.getActionEventsByCourse(this.course.id, lastEventId, this.search ?? '') | ||||
|             : await AddonBlockTimeline.getActionEventsByTimesort(lastEventId, this.search ?? ''); | ||||
| 
 | ||||
|         this.data$.next({ | ||||
|             events: this.data$.value.events.concat(this.reduceEvents(events, this.overdue, this.dateRange)), | ||||
|         this.dataSubject$.next({ | ||||
|             events: this.dataSubject$.value.events.concat(await this.reduceEvents(events, this.overdue, this.dateRange)), | ||||
|             lastEventId: canLoadMore, | ||||
|             canLoadMore: canLoadMore !== undefined, | ||||
|             loadingMore: false, | ||||
| @ -85,32 +94,35 @@ export class AddonBlockTimelineSection { | ||||
|      * @param dateRange Date range to filter events. | ||||
|      * @returns Day events list. | ||||
|      */ | ||||
|     private reduceEvents( | ||||
|     private async reduceEvents( | ||||
|         events: AddonCalendarEvent[], | ||||
|         overdue: boolean, | ||||
|         { from, to }: AddonBlockTimelineDateRange, | ||||
|     ): AddonBlockTimelineDayEvents[] { | ||||
|     ): Promise<AddonBlockTimelineDayEvents[]> { | ||||
|         const filterDates: AddonBlockTimelineFilterDates = { | ||||
|             now: CoreTimeUtils.timestamp(), | ||||
|             midnight: AddonBlockTimeline.getDayStart(), | ||||
|             start: AddonBlockTimeline.getDayStart(from), | ||||
|             end: typeof to === 'number' ? AddonBlockTimeline.getDayStart(to) : undefined, | ||||
|         }; | ||||
|         const eventsByDates = events | ||||
|             .filter((event) => this.filterEvent(event, overdue, filterDates)) | ||||
|             .map((event) => this.mapToTimelineEvent(event, filterDates.now)) | ||||
|             .reduce((filteredEvents, event) => { | ||||
|                 const dayTimestamp = CoreTimeUtils.getMidnightForTimestamp(event.timesort); | ||||
|         const timelineEvents = await Promise.all( | ||||
|             events | ||||
|                 .filter((event) => this.filterEvent(event, overdue, filterDates)) | ||||
|                 .map((event) => this.mapToTimelineEvent(event, filterDates.now)), | ||||
|         ); | ||||
| 
 | ||||
|                 filteredEvents[dayTimestamp] = filteredEvents[dayTimestamp] ?? { | ||||
|                     dayTimestamp, | ||||
|                     events: [], | ||||
|                 } as AddonBlockTimelineDayEvents; | ||||
|         const eventsByDates = timelineEvents.reduce((filteredEvents, event) => { | ||||
|             const dayTimestamp = CoreTimeUtils.getMidnightForTimestamp(event.timesort); | ||||
| 
 | ||||
|                 filteredEvents[dayTimestamp].events.push(event); | ||||
|             filteredEvents[dayTimestamp] = filteredEvents[dayTimestamp] ?? { | ||||
|                 dayTimestamp, | ||||
|                 events: [], | ||||
|             } as AddonBlockTimelineDayEvents; | ||||
| 
 | ||||
|                 return filteredEvents; | ||||
|             }, {} as Record<string, AddonBlockTimelineDayEvents>); | ||||
|             filteredEvents[dayTimestamp].events.push(event); | ||||
| 
 | ||||
|             return filteredEvents; | ||||
|         }, {} as Record<string, AddonBlockTimelineDayEvents>); | ||||
| 
 | ||||
|         return Object.values(eventsByDates); | ||||
|     } | ||||
| @ -151,20 +163,30 @@ export class AddonBlockTimelineSection { | ||||
|      * @param now Current time. | ||||
|      * @returns Timeline event. | ||||
|      */ | ||||
|     private mapToTimelineEvent(event: AddonCalendarEvent, now: number): AddonBlockTimelineEvent { | ||||
|     private async mapToTimelineEvent(event: AddonCalendarEvent, now: number): Promise<AddonBlockTimelineEvent> { | ||||
|         const modulename = event.modulename || event.icon.component; | ||||
| 
 | ||||
|         return { | ||||
|             ...event, | ||||
|             modulename, | ||||
|             overdue: event.timesort < now, | ||||
|             iconUrl: CoreCourse.getModuleIconSrc(event.icon.component), | ||||
|             iconUrl: await CoreCourseModuleDelegate.getModuleIconSrc(event.icon.component, event.icon.iconurl), | ||||
|             iconTitle: CoreCourse.translateModuleName(modulename), | ||||
|         } as AddonBlockTimelineEvent; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Section data. | ||||
|  */ | ||||
| export type AddonBlockTimelineSectionData = { | ||||
|     events: AddonBlockTimelineDayEvents[]; | ||||
|     lastEventId?: number; | ||||
|     canLoadMore: boolean; | ||||
|     loadingMore: boolean; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Timestamps to use during event filtering. | ||||
|  */ | ||||
|  | ||||
| @ -22,7 +22,7 @@ import { CoreCoursesHelper, CoreEnrolledCourseDataWithOptions } from '@features/ | ||||
| import { CoreCourses } from '@features/courses/services/courses'; | ||||
| import { CoreCourseOptionsDelegate } from '@features/course/services/course-options-delegate'; | ||||
| import { BehaviorSubject, combineLatest, Observable, of, Subject } from 'rxjs'; | ||||
| import { catchError, distinctUntilChanged, map, share, tap } from 'rxjs/operators'; | ||||
| import { catchError, distinctUntilChanged, map, share, tap, mergeAll } from 'rxjs/operators'; | ||||
| import { AddonBlockTimelineDateRange, AddonBlockTimelineSection } from '@addons/block/timeline/classes/section'; | ||||
| import { FormControl } from '@angular/forms'; | ||||
| import { formControlValue, resolved } from '@/core/utils/rxjs'; | ||||
| @ -198,6 +198,7 @@ export class AddonBlockTimelineComponent implements OnInit, ICoreBlockComponent | ||||
|                 } | ||||
|             }), | ||||
|             resolved(), | ||||
|             mergeAll(), | ||||
|             catchError(error => { | ||||
|                 // An error ocurred in the function, log the error and just resolve the observable so the workflow continues.
 | ||||
|                 this.logger.error(error); | ||||
| @ -205,7 +206,7 @@ export class AddonBlockTimelineComponent implements OnInit, ICoreBlockComponent | ||||
|                 // Error getting data, fail.
 | ||||
|                 CoreDomUtils.showErrorModalDefault(error, this.fetchContentDefaultError, true); | ||||
| 
 | ||||
|                 return of([]); | ||||
|                 return of([] as AddonBlockTimelineSection[]); | ||||
|             }), | ||||
|             share(), | ||||
|             tap(() => (this.loaded = true)), | ||||
| @ -224,12 +225,12 @@ export class AddonBlockTimelineComponent implements OnInit, ICoreBlockComponent | ||||
|         search: string | null, | ||||
|         overdue: boolean, | ||||
|         dateRange: AddonBlockTimelineDateRange, | ||||
|     ): Promise<AddonBlockTimelineSection[]> { | ||||
|     ): Promise<Observable<AddonBlockTimelineSection[]>> { | ||||
|         const section = new AddonBlockTimelineSection(search, overdue, dateRange); | ||||
| 
 | ||||
|         await section.loadMore(); | ||||
| 
 | ||||
|         return section.data$.value.events.length > 0 ? [section] : []; | ||||
|         return section.data$.pipe(map(({ events }) => events.length > 0 ? [section] : [])); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -246,29 +247,38 @@ export class AddonBlockTimelineComponent implements OnInit, ICoreBlockComponent | ||||
|         overdue: boolean, | ||||
|         dateRange: AddonBlockTimelineDateRange, | ||||
|         courses: CoreEnrolledCourseDataWithOptions[], | ||||
|     ): Promise<AddonBlockTimelineSection[]> { | ||||
|     ): Promise<Observable<AddonBlockTimelineSection[]>> { | ||||
|         // Do not filter courses by date because they can contain activities due.
 | ||||
|         const courseIds = courses.map(course => course.id); | ||||
|         const gracePeriod = await this.getCoursesGracePeriod(); | ||||
|         const courseEvents = await AddonBlockTimeline.getActionEventsByCourses(courseIds, search ?? ''); | ||||
| 
 | ||||
|         return courses | ||||
|             .filter( | ||||
|                 course => | ||||
|                     !course.hidden && | ||||
|                     !CoreCoursesHelper.isPastCourse(course, gracePeriod.after) && | ||||
|                     !CoreCoursesHelper.isFutureCourse(course, gracePeriod.after, gracePeriod.before) && | ||||
|                     courseEvents[course.id].events.length > 0, | ||||
|             ) | ||||
|             .map(course => new AddonBlockTimelineSection( | ||||
|                 search, | ||||
|                 overdue, | ||||
|                 dateRange, | ||||
|                 course, | ||||
|                 courseEvents[course.id].events, | ||||
|                 courseEvents[course.id].canLoadMore, | ||||
|             )) | ||||
|             .filter(section => section.data$.value.events.length > 0); | ||||
|         return combineLatest( | ||||
|             courses | ||||
|                 .filter( | ||||
|                     course => | ||||
|                         !course.hidden && | ||||
|                         !CoreCoursesHelper.isPastCourse(course, gracePeriod.after) && | ||||
|                         !CoreCoursesHelper.isFutureCourse(course, gracePeriod.after, gracePeriod.before) && | ||||
|                         courseEvents[course.id].events.length > 0, | ||||
|                 ) | ||||
|                 .map(course => { | ||||
|                     const section = new AddonBlockTimelineSection( | ||||
|                         search, | ||||
|                         overdue, | ||||
|                         dateRange, | ||||
|                         course, | ||||
|                         courseEvents[course.id].events, | ||||
|                         courseEvents[course.id].canLoadMore, | ||||
|                     ); | ||||
| 
 | ||||
|                     return section.data$.pipe(map(({ events }) => events.length > 0 ? section : null)); | ||||
|                 }), | ||||
|         ).pipe( | ||||
|             map(sections => sections.filter( | ||||
|                 (section: AddonBlockTimelineSection | null): section is AddonBlockTimelineSection => !!section, | ||||
|             )), | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | ||||
| @ -551,7 +551,9 @@ class AddonCalendarMonthSlidesItemsManagerSource extends CoreSwipeSlidesDynamicI | ||||
|                 day.eventsFormated = day.eventsFormated || []; | ||||
|                 day.filteredEvents = day.filteredEvents || []; | ||||
|                 // Format online events.
 | ||||
|                 const onlineEventsFormatted = day.events.map((event) => AddonCalendarHelper.formatEventData(event)); | ||||
|                 const onlineEventsFormatted = await Promise.all( | ||||
|                     day.events.map((event) => AddonCalendarHelper.formatEventData(event)), | ||||
|                 ); | ||||
| 
 | ||||
|                 day.eventsFormated = day.eventsFormated.concat(onlineEventsFormatted); | ||||
| 
 | ||||
|  | ||||
| @ -165,7 +165,7 @@ export class AddonCalendarUpcomingEventsComponent implements OnInit, DoCheck, On | ||||
|     async fetchEvents(): Promise<void> { | ||||
|         // Don't pass courseId and categoryId, we'll filter them locally.
 | ||||
|         const result = await AddonCalendar.getUpcomingEvents(); | ||||
|         this.onlineEvents = result.events.map((event) => AddonCalendarHelper.formatEventData(event)); | ||||
|         this.onlineEvents = await Promise.all(result.events.map((event) => AddonCalendarHelper.formatEventData(event))); | ||||
|         // Merge the online events with offline data.
 | ||||
|         this.events = this.mergeEvents(); | ||||
|         // Filter events by course.
 | ||||
|  | ||||
| @ -672,7 +672,7 @@ class AddonCalendarDaySlidesItemsManagerSource extends CoreSwipeSlidesDynamicIte | ||||
|         try { | ||||
|             // Don't pass courseId and categoryId, we'll filter them locally.
 | ||||
|             result = await AddonCalendar.getDayEvents(day.moment.year(), day.moment.month() + 1, day.moment.date()); | ||||
|             preloadedDay.onlineEvents = result.events.map((event) => AddonCalendarHelper.formatEventData(event)); | ||||
|             preloadedDay.onlineEvents = await Promise.all(result.events.map((event) => AddonCalendarHelper.formatEventData(event))); | ||||
|         } catch (error) { | ||||
|             // Allow navigating to non-cached days in offline (behave as if using emergency cache).
 | ||||
|             if (CoreNetwork.isOnline()) { | ||||
|  | ||||
| @ -197,7 +197,7 @@ export class AddonCalendarEventPage implements OnInit, OnDestroy { | ||||
|             // Get the event data.
 | ||||
|             if (this.eventId >= 0) { | ||||
|                 const event = await AddonCalendar.getEventById(this.eventId); | ||||
|                 this.event = AddonCalendarHelper.formatEventData(event); | ||||
|                 this.event = await AddonCalendarHelper.formatEventData(event); | ||||
|             } | ||||
| 
 | ||||
|             try { | ||||
|  | ||||
| @ -37,6 +37,7 @@ import { AddonCalendarOfflineEventDBRecord } from './database/calendar-offline'; | ||||
| import { CoreCategoryData } from '@features/courses/services/courses'; | ||||
| import { CoreTimeUtils } from '@services/utils/time'; | ||||
| import { CoreReminders, CoreRemindersService } from '@features/reminders/services/reminders'; | ||||
| import { CoreCourseModuleDelegate } from '@features/course/services/module-delegate'; | ||||
| 
 | ||||
| /** | ||||
|  * Context levels enumeration. | ||||
| @ -164,9 +165,9 @@ export class AddonCalendarHelperProvider { | ||||
|      * @param event Event to format. | ||||
|      * @returns The formatted event to display. | ||||
|      */ | ||||
|     formatEventData( | ||||
|     async formatEventData( | ||||
|         event: AddonCalendarEvent | AddonCalendarEventBase | AddonCalendarGetEventsEvent, | ||||
|     ): AddonCalendarEventToDisplay { | ||||
|     ): Promise<AddonCalendarEventToDisplay> { | ||||
| 
 | ||||
|         const eventFormatted: AddonCalendarEventToDisplay = { | ||||
|             ...event, | ||||
| @ -182,7 +183,10 @@ export class AddonCalendarHelperProvider { | ||||
|         }; | ||||
| 
 | ||||
|         if (event.modulename) { | ||||
|             eventFormatted.eventIcon = CoreCourse.getModuleIconSrc(event.modulename); | ||||
|             eventFormatted.eventIcon = await CoreCourseModuleDelegate.getModuleIconSrc( | ||||
|                 event.modulename, | ||||
|                 'icon' in event ? event.icon.iconurl : undefined, | ||||
|             ); | ||||
|             eventFormatted.moduleIcon = eventFormatted.eventIcon; | ||||
|             eventFormatted.iconTitle = CoreCourse.translateModuleName(event.modulename); | ||||
|         } | ||||
|  | ||||
| @ -1809,6 +1809,7 @@ export type AddonCalendarEventBase = { | ||||
|         key: string; // Key.
 | ||||
|         component: string; // Component.
 | ||||
|         alttext: string; // Alttext.
 | ||||
|         iconurl?: string; // @since 4.2. Icon image url.
 | ||||
|     }; | ||||
|     category?: { | ||||
|         id: number; // Id.
 | ||||
|  | ||||
| @ -52,7 +52,7 @@ export class AddonModLabelModuleHandlerService extends CoreModuleHandlerBase imp | ||||
|         module.description = ''; | ||||
| 
 | ||||
|         return { | ||||
|             icon: '', | ||||
|             icon: this.getIconSrc(), | ||||
|             title, | ||||
|             a11yTitle: '', | ||||
|             class: 'addon-mod-label-handler', | ||||
| @ -74,5 +74,12 @@ export class AddonModLabelModuleHandlerService extends CoreModuleHandlerBase imp | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     getIconSrc(): string { | ||||
|         return ''; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| export const AddonModLabelModuleHandler = makeSingleton(AddonModLabelModuleHandlerService); | ||||
|  | ||||
| @ -58,10 +58,6 @@ export class AddonModLtiModuleHandlerService extends CoreModuleHandlerBase imple | ||||
|     ): Promise<CoreCourseModuleHandlerData> { | ||||
|         const data = await super.getData(module, courseId, sectionId, forCoursePage); | ||||
|         data.showDownloadButton = false; | ||||
| 
 | ||||
|         // Handle custom icons.
 | ||||
|         data.icon =  module.modicon; | ||||
| 
 | ||||
|         data.buttons = [{ | ||||
|             icon: 'fas-external-link-alt', | ||||
|             label: 'addon.mod_lti.launchactivity', | ||||
| @ -83,6 +79,13 @@ export class AddonModLtiModuleHandlerService extends CoreModuleHandlerBase imple | ||||
|         return AddonModLtiIndexComponent; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     getIconSrc(module?: CoreCourseModuleData | undefined, modicon?: string | undefined): string | undefined { | ||||
|         return module?.modicon ?? modicon ?? CoreCourse.getModuleIconSrc(this.modName); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| export const AddonModLtiModuleHandler = makeSingleton(AddonModLtiModuleHandlerService); | ||||
|  | ||||
| @ -41,7 +41,7 @@ export class CoreModuleHandlerBase implements Partial<CoreCourseModuleHandler> { | ||||
|         forCoursePage?: boolean, // eslint-disable-line @typescript-eslint/no-unused-vars
 | ||||
|     ): Promise<CoreCourseModuleHandlerData> | CoreCourseModuleHandlerData { | ||||
|         return { | ||||
|             icon: CoreCourse.getModuleIconSrc(module.modname, module.modicon), | ||||
|             icon: this.getIconSrc(module, module.modicon), | ||||
|             title: module.name, | ||||
|             class: 'addon-mod_' + module.modname + '-handler', | ||||
|             showDownloadButton: true, | ||||
| @ -78,4 +78,15 @@ export class CoreModuleHandlerBase implements Partial<CoreCourseModuleHandler> { | ||||
|         await CoreNavigator.navigateToSitePath(this.pageName + routeParams, options); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     getIconSrc(module?: CoreCourseModuleData, modicon?: string): Promise<string | undefined> | string | undefined { | ||||
|         if (!module) { | ||||
|             return modicon; | ||||
|         } | ||||
| 
 | ||||
|         return CoreCourse.getModuleIconSrc(module.name, modicon); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -82,9 +82,10 @@ export interface CoreCourseModuleHandler extends CoreDelegateHandler { | ||||
|      * Get the icon src for the module. | ||||
|      * | ||||
|      * @param module Module to get the icon from. | ||||
|      * @param modicon The mod icon string. | ||||
|      * @returns The icon src. | ||||
|      */ | ||||
|     getIconSrc?(module?: CoreCourseModuleData): Promise<string | undefined> | string | undefined; | ||||
|     getIconSrc?(module?: CoreCourseModuleData, modicon?: string): Promise<string | undefined> | string | undefined; | ||||
| 
 | ||||
|     /** | ||||
|      * Check if this type of module supports a certain feature. | ||||
| @ -390,9 +391,9 @@ export class CoreCourseModuleDelegateService extends CoreDelegate<CoreCourseModu | ||||
|      * @returns Promise resolved with the icon src. | ||||
|      */ | ||||
|     async getModuleIconSrc(modname: string, modicon?: string, module?: CoreCourseModuleData): Promise<string> { | ||||
|         const icon = await this.executeFunctionOnEnabled<Promise<string>>(modname, 'getIconSrc', [module]); | ||||
|         const icon = await this.executeFunctionOnEnabled<Promise<string>>(modname, 'getIconSrc', [module, modicon]); | ||||
| 
 | ||||
|         return icon || CoreCourse.getModuleIconSrc(modname, modicon) || ''; | ||||
|         return icon ?? CoreCourse.getModuleIconSrc(modname, modicon) ?? ''; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | ||||
| @ -206,7 +206,7 @@ export class CoreGradesCoursePage implements AfterViewInit, OnDestroy { | ||||
|      */ | ||||
|     private async fetchGrades(): Promise<void> { | ||||
|         const table = await CoreGrades.getCourseGradesTable(this.courseId, this.userId); | ||||
|         const formattedTable = CoreGradesHelper.formatGradesTable(table); | ||||
|         const formattedTable = await CoreGradesHelper.formatGradesTable(table); | ||||
| 
 | ||||
|         this.title = formattedTable.rows[0]?.gradeitem ?? Translate.instant('core.grades.grades'); | ||||
|         this.columns = formattedTable.columns; | ||||
|  | ||||
| @ -37,6 +37,7 @@ import { makeSingleton, Translate } from '@singletons'; | ||||
| import { CoreError } from '@classes/errors/error'; | ||||
| import { CoreCourseHelper } from '@features/course/services/course-helper'; | ||||
| import { CoreAppProvider } from '@services/app'; | ||||
| import { CoreCourseModuleDelegate } from '@features/course/services/module-delegate'; | ||||
| 
 | ||||
| export const GRADES_PAGE_NAME = 'grades'; | ||||
| 
 | ||||
| @ -73,7 +74,7 @@ export class CoreGradesHelperProvider { | ||||
|             let content = String(column.content); | ||||
| 
 | ||||
|             if (name == 'itemname') { | ||||
|                 this.setRowIconAndType(row, content); | ||||
|                 await this.setRowIconAndType(row, content); | ||||
| 
 | ||||
|                 row.link = this.getModuleLink(content); | ||||
|                 row.rowclass += column.class.indexOf('hidden') >= 0 ? ' hidden' : ''; | ||||
| @ -102,7 +103,10 @@ export class CoreGradesHelperProvider { | ||||
|      * @param useLegacyLayout Whether to use the layout before 4.1. | ||||
|      * @returns Formatted row object. | ||||
|      */ | ||||
|     protected formatGradeRowForTable(tableRow: CoreGradesTableRow, useLegacyLayout: boolean): CoreGradesFormattedTableRow { | ||||
|     protected async formatGradeRowForTable( | ||||
|         tableRow: CoreGradesTableRow, | ||||
|         useLegacyLayout: boolean, | ||||
|     ): Promise<CoreGradesFormattedTableRow> { | ||||
|         const row: CoreGradesFormattedTableRow = {}; | ||||
| 
 | ||||
|         if (!useLegacyLayout && 'leader' in tableRow) { | ||||
| @ -132,7 +136,7 @@ export class CoreGradesHelperProvider { | ||||
|                 row.colspan = itemNameColumn.colspan; | ||||
|                 row.rowspan = tableRow.leader?.rowspan || 1; | ||||
| 
 | ||||
|                 this.setRowIconAndType(row, content); | ||||
|                 await this.setRowIconAndType(row, content); | ||||
|                 this.setRowStyleClasses(row, itemNameColumn.class); | ||||
|                 row.rowclass += itemNameColumn.class.indexOf('hidden') >= 0 ? ' hidden' : ''; | ||||
|                 row.rowclass += itemNameColumn.class.indexOf('dimmed_text') >= 0 ? ' dimmed_text' : ''; | ||||
| @ -203,7 +207,7 @@ export class CoreGradesHelperProvider { | ||||
|      * @param table JSON object representing a table with data. | ||||
|      * @returns Formatted HTML table. | ||||
|      */ | ||||
|     formatGradesTable(table: CoreGradesTable): CoreGradesFormattedTable { | ||||
|     async formatGradesTable(table: CoreGradesTable): Promise<CoreGradesFormattedTable> { | ||||
|         const maxDepth = table.maxdepth; | ||||
|         const formatted: CoreGradesFormattedTable = { | ||||
|             columns: [], | ||||
| @ -223,7 +227,7 @@ export class CoreGradesHelperProvider { | ||||
|             feedback: false, | ||||
|             contributiontocoursetotal: false, | ||||
|         }; | ||||
|         formatted.rows = this.formatGradesTableRows(table.tabledata); | ||||
|         formatted.rows = await this.formatGradesTableRows(table.tabledata); | ||||
| 
 | ||||
|         // Get a row with some info.
 | ||||
|         let normalRow = formatted.rows.find( | ||||
| @ -261,9 +265,9 @@ export class CoreGradesHelperProvider { | ||||
|      * @param rows Unformatted rows. | ||||
|      * @returns Formatted rows. | ||||
|      */ | ||||
|     protected formatGradesTableRows(rows: CoreGradesTableRow[]): CoreGradesFormattedTableRow[] { | ||||
|     protected async formatGradesTableRows(rows: CoreGradesTableRow[]): Promise<CoreGradesFormattedTableRow[]> { | ||||
|         const useLegacyLayout = !CoreSites.getRequiredCurrentSite().isVersionGreaterEqualThan('4.1'); | ||||
|         const formattedRows = rows.map(row => this.formatGradeRowForTable(row, useLegacyLayout)); | ||||
|         const formattedRows = await Promise.all(rows.map(row => this.formatGradeRowForTable(row, useLegacyLayout))); | ||||
| 
 | ||||
|         if (!useLegacyLayout) { | ||||
|             for (let index = 0; index < formattedRows.length - 1; index++) { | ||||
| @ -652,7 +656,7 @@ export class CoreGradesHelperProvider { | ||||
|      * @param row Row. | ||||
|      * @param text Row content. | ||||
|      */ | ||||
|     protected setRowIconAndType(row: CoreGradesFormattedRowCommonData, text: string): void { | ||||
|     protected async setRowIconAndType(row: CoreGradesFormattedRowCommonData, text: string): Promise<void> { | ||||
|         text = text.replace('%2F', '/').replace('%2f', '/'); | ||||
|         if (text.indexOf('/agg_mean') > -1) { | ||||
|             row.itemtype = 'agg_mean'; | ||||
| @ -684,7 +688,7 @@ export class CoreGradesHelperProvider { | ||||
|                 row.itemtype = 'mod'; | ||||
|                 row.itemmodule = module[1]; | ||||
|                 row.iconAlt = CoreCourse.translateModuleName(row.itemmodule) || ''; | ||||
|                 row.image = CoreCourse.getModuleIconSrc( | ||||
|                 row.image = await CoreCourseModuleDelegate.getModuleIconSrc( | ||||
|                     module[1], | ||||
|                     CoreDomUtils.convertToElement(text).querySelector('img')?.getAttribute('src') ?? undefined, | ||||
|                 ); | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user