MOBILE-4193 core: Consolidate module icons sources

main
Noel De Martin 2023-03-14 13:52:23 +01:00
parent 2bedc67695
commit 0c998b8f8b
15 changed files with 147 additions and 81 deletions

View File

@ -18,6 +18,7 @@ import { CoreDomUtils } from '@services/utils/dom';
import { CoreCourse } from '@features/course/services/course'; import { CoreCourse } from '@features/course/services/course';
import { CoreSiteWSPreSets } from '@classes/site'; import { CoreSiteWSPreSets } from '@classes/site';
import { makeSingleton } from '@singletons'; import { makeSingleton } from '@singletons';
import { CoreCourseModuleDelegate } from '@features/course/services/module-delegate';
const ROOT_CACHE_KEY = 'AddonBlockRecentlyAccessedItems:'; const ROOT_CACHE_KEY = 'AddonBlockRecentlyAccessedItems:';
@ -54,15 +55,15 @@ export class AddonBlockRecentlyAccessedItemsProvider {
const cmIds: number[] = []; const cmIds: number[] = [];
items = items.map((item) => { items = await Promise.all(items.map(async (item) => {
const modicon = item.icon && CoreDomUtils.getHTMLElementAttribute(item.icon, 'src'); 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'); item.iconTitle = item.icon && CoreDomUtils.getHTMLElementAttribute(item.icon, 'title');
cmIds.push(item.cmid); cmIds.push(item.cmid);
return item; return item;
}); }));
// Check if the viewed module should be updated for each activity. // Check if the viewed module should be updated for each activity.
const lastViewedMap = await CoreCourse.getCertainModulesViewed(cmIds, site.getId()); const lastViewedMap = await CoreCourse.getCertainModulesViewed(cmIds, site.getId());

View File

@ -15,9 +15,10 @@
import { AddonBlockTimeline } from '@addons/block/timeline/services/timeline'; import { AddonBlockTimeline } from '@addons/block/timeline/services/timeline';
import { AddonCalendarEvent } from '@addons/calendar/services/calendar'; import { AddonCalendarEvent } from '@addons/calendar/services/calendar';
import { CoreCourse } from '@features/course/services/course'; import { CoreCourse } from '@features/course/services/course';
import { CoreCourseModuleDelegate } from '@features/course/services/module-delegate';
import { CoreEnrolledCourseDataWithOptions } from '@features/courses/services/courses-helper'; import { CoreEnrolledCourseDataWithOptions } from '@features/courses/services/courses-helper';
import { CoreTimeUtils } from '@services/utils/time'; import { CoreTimeUtils } from '@services/utils/time';
import { BehaviorSubject } from 'rxjs'; import { BehaviorSubject, Observable } from 'rxjs';
/** /**
* A collection of events displayed in the timeline block. * A collection of events displayed in the timeline block.
@ -28,12 +29,8 @@ export class AddonBlockTimelineSection {
overdue: boolean; overdue: boolean;
dateRange: AddonBlockTimelineDateRange; dateRange: AddonBlockTimelineDateRange;
course?: CoreEnrolledCourseDataWithOptions; course?: CoreEnrolledCourseDataWithOptions;
data$: BehaviorSubject<{
events: AddonBlockTimelineDayEvents[]; private dataSubject$: BehaviorSubject<AddonBlockTimelineSectionData>;
lastEventId?: number;
canLoadMore: boolean;
loadingMore: boolean;
}>;
constructor( constructor(
search: string | null, search: string | null,
@ -47,30 +44,42 @@ export class AddonBlockTimelineSection {
this.overdue = overdue; this.overdue = overdue;
this.dateRange = dateRange; this.dateRange = dateRange;
this.course = course; this.course = course;
this.data$ = new BehaviorSubject({ this.dataSubject$ = new BehaviorSubject({
events: courseEvents ? this.reduceEvents(courseEvents, overdue, dateRange) : [], events: [],
lastEventId: canLoadMore, lastEventId: canLoadMore,
canLoadMore: typeof canLoadMore !== 'undefined', canLoadMore: typeof canLoadMore !== 'undefined',
loadingMore: false, 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. * Load more events.
*/ */
async loadMore(): Promise<void> { async loadMore(): Promise<void> {
this.data$.next({ this.dataSubject$.next({
...this.data$.value, ...this.dataSubject$.value,
loadingMore: true, loadingMore: true,
}); });
const lastEventId = this.data$.value.lastEventId; const lastEventId = this.dataSubject$.value.lastEventId;
const { events, canLoadMore } = this.course const { events, canLoadMore } = this.course
? await AddonBlockTimeline.getActionEventsByCourse(this.course.id, lastEventId, this.search ?? '') ? await AddonBlockTimeline.getActionEventsByCourse(this.course.id, lastEventId, this.search ?? '')
: await AddonBlockTimeline.getActionEventsByTimesort(lastEventId, this.search ?? ''); : await AddonBlockTimeline.getActionEventsByTimesort(lastEventId, this.search ?? '');
this.data$.next({ this.dataSubject$.next({
events: this.data$.value.events.concat(this.reduceEvents(events, this.overdue, this.dateRange)), events: this.dataSubject$.value.events.concat(await this.reduceEvents(events, this.overdue, this.dateRange)),
lastEventId: canLoadMore, lastEventId: canLoadMore,
canLoadMore: canLoadMore !== undefined, canLoadMore: canLoadMore !== undefined,
loadingMore: false, loadingMore: false,
@ -85,21 +94,24 @@ export class AddonBlockTimelineSection {
* @param dateRange Date range to filter events. * @param dateRange Date range to filter events.
* @returns Day events list. * @returns Day events list.
*/ */
private reduceEvents( private async reduceEvents(
events: AddonCalendarEvent[], events: AddonCalendarEvent[],
overdue: boolean, overdue: boolean,
{ from, to }: AddonBlockTimelineDateRange, { from, to }: AddonBlockTimelineDateRange,
): AddonBlockTimelineDayEvents[] { ): Promise<AddonBlockTimelineDayEvents[]> {
const filterDates: AddonBlockTimelineFilterDates = { const filterDates: AddonBlockTimelineFilterDates = {
now: CoreTimeUtils.timestamp(), now: CoreTimeUtils.timestamp(),
midnight: AddonBlockTimeline.getDayStart(), midnight: AddonBlockTimeline.getDayStart(),
start: AddonBlockTimeline.getDayStart(from), start: AddonBlockTimeline.getDayStart(from),
end: typeof to === 'number' ? AddonBlockTimeline.getDayStart(to) : undefined, end: typeof to === 'number' ? AddonBlockTimeline.getDayStart(to) : undefined,
}; };
const eventsByDates = events const timelineEvents = await Promise.all(
events
.filter((event) => this.filterEvent(event, overdue, filterDates)) .filter((event) => this.filterEvent(event, overdue, filterDates))
.map((event) => this.mapToTimelineEvent(event, filterDates.now)) .map((event) => this.mapToTimelineEvent(event, filterDates.now)),
.reduce((filteredEvents, event) => { );
const eventsByDates = timelineEvents.reduce((filteredEvents, event) => {
const dayTimestamp = CoreTimeUtils.getMidnightForTimestamp(event.timesort); const dayTimestamp = CoreTimeUtils.getMidnightForTimestamp(event.timesort);
filteredEvents[dayTimestamp] = filteredEvents[dayTimestamp] ?? { filteredEvents[dayTimestamp] = filteredEvents[dayTimestamp] ?? {
@ -151,20 +163,30 @@ export class AddonBlockTimelineSection {
* @param now Current time. * @param now Current time.
* @returns Timeline event. * @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; const modulename = event.modulename || event.icon.component;
return { return {
...event, ...event,
modulename, modulename,
overdue: event.timesort < now, overdue: event.timesort < now,
iconUrl: CoreCourse.getModuleIconSrc(event.icon.component), iconUrl: await CoreCourseModuleDelegate.getModuleIconSrc(event.icon.component, event.icon.iconurl),
iconTitle: CoreCourse.translateModuleName(modulename), iconTitle: CoreCourse.translateModuleName(modulename),
} as AddonBlockTimelineEvent; } as AddonBlockTimelineEvent;
} }
} }
/**
* Section data.
*/
export type AddonBlockTimelineSectionData = {
events: AddonBlockTimelineDayEvents[];
lastEventId?: number;
canLoadMore: boolean;
loadingMore: boolean;
};
/** /**
* Timestamps to use during event filtering. * Timestamps to use during event filtering.
*/ */

View File

@ -22,7 +22,7 @@ import { CoreCoursesHelper, CoreEnrolledCourseDataWithOptions } from '@features/
import { CoreCourses } from '@features/courses/services/courses'; import { CoreCourses } from '@features/courses/services/courses';
import { CoreCourseOptionsDelegate } from '@features/course/services/course-options-delegate'; import { CoreCourseOptionsDelegate } from '@features/course/services/course-options-delegate';
import { BehaviorSubject, combineLatest, Observable, of, Subject } from 'rxjs'; 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 { AddonBlockTimelineDateRange, AddonBlockTimelineSection } from '@addons/block/timeline/classes/section';
import { FormControl } from '@angular/forms'; import { FormControl } from '@angular/forms';
import { formControlValue, resolved } from '@/core/utils/rxjs'; import { formControlValue, resolved } from '@/core/utils/rxjs';
@ -198,6 +198,7 @@ export class AddonBlockTimelineComponent implements OnInit, ICoreBlockComponent
} }
}), }),
resolved(), resolved(),
mergeAll(),
catchError(error => { catchError(error => {
// An error ocurred in the function, log the error and just resolve the observable so the workflow continues. // An error ocurred in the function, log the error and just resolve the observable so the workflow continues.
this.logger.error(error); this.logger.error(error);
@ -205,7 +206,7 @@ export class AddonBlockTimelineComponent implements OnInit, ICoreBlockComponent
// Error getting data, fail. // Error getting data, fail.
CoreDomUtils.showErrorModalDefault(error, this.fetchContentDefaultError, true); CoreDomUtils.showErrorModalDefault(error, this.fetchContentDefaultError, true);
return of([]); return of([] as AddonBlockTimelineSection[]);
}), }),
share(), share(),
tap(() => (this.loaded = true)), tap(() => (this.loaded = true)),
@ -224,12 +225,12 @@ export class AddonBlockTimelineComponent implements OnInit, ICoreBlockComponent
search: string | null, search: string | null,
overdue: boolean, overdue: boolean,
dateRange: AddonBlockTimelineDateRange, dateRange: AddonBlockTimelineDateRange,
): Promise<AddonBlockTimelineSection[]> { ): Promise<Observable<AddonBlockTimelineSection[]>> {
const section = new AddonBlockTimelineSection(search, overdue, dateRange); const section = new AddonBlockTimelineSection(search, overdue, dateRange);
await section.loadMore(); await section.loadMore();
return section.data$.value.events.length > 0 ? [section] : []; return section.data$.pipe(map(({ events }) => events.length > 0 ? [section] : []));
} }
/** /**
@ -246,13 +247,14 @@ export class AddonBlockTimelineComponent implements OnInit, ICoreBlockComponent
overdue: boolean, overdue: boolean,
dateRange: AddonBlockTimelineDateRange, dateRange: AddonBlockTimelineDateRange,
courses: CoreEnrolledCourseDataWithOptions[], courses: CoreEnrolledCourseDataWithOptions[],
): Promise<AddonBlockTimelineSection[]> { ): Promise<Observable<AddonBlockTimelineSection[]>> {
// Do not filter courses by date because they can contain activities due. // Do not filter courses by date because they can contain activities due.
const courseIds = courses.map(course => course.id); const courseIds = courses.map(course => course.id);
const gracePeriod = await this.getCoursesGracePeriod(); const gracePeriod = await this.getCoursesGracePeriod();
const courseEvents = await AddonBlockTimeline.getActionEventsByCourses(courseIds, search ?? ''); const courseEvents = await AddonBlockTimeline.getActionEventsByCourses(courseIds, search ?? '');
return courses return combineLatest(
courses
.filter( .filter(
course => course =>
!course.hidden && !course.hidden &&
@ -260,15 +262,23 @@ export class AddonBlockTimelineComponent implements OnInit, ICoreBlockComponent
!CoreCoursesHelper.isFutureCourse(course, gracePeriod.after, gracePeriod.before) && !CoreCoursesHelper.isFutureCourse(course, gracePeriod.after, gracePeriod.before) &&
courseEvents[course.id].events.length > 0, courseEvents[course.id].events.length > 0,
) )
.map(course => new AddonBlockTimelineSection( .map(course => {
const section = new AddonBlockTimelineSection(
search, search,
overdue, overdue,
dateRange, dateRange,
course, course,
courseEvents[course.id].events, courseEvents[course.id].events,
courseEvents[course.id].canLoadMore, courseEvents[course.id].canLoadMore,
)) );
.filter(section => section.data$.value.events.length > 0);
return section.data$.pipe(map(({ events }) => events.length > 0 ? section : null));
}),
).pipe(
map(sections => sections.filter(
(section: AddonBlockTimelineSection | null): section is AddonBlockTimelineSection => !!section,
)),
);
} }
/** /**

View File

@ -551,7 +551,9 @@ class AddonCalendarMonthSlidesItemsManagerSource extends CoreSwipeSlidesDynamicI
day.eventsFormated = day.eventsFormated || []; day.eventsFormated = day.eventsFormated || [];
day.filteredEvents = day.filteredEvents || []; day.filteredEvents = day.filteredEvents || [];
// Format online events. // 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); day.eventsFormated = day.eventsFormated.concat(onlineEventsFormatted);

View File

@ -165,7 +165,7 @@ export class AddonCalendarUpcomingEventsComponent implements OnInit, DoCheck, On
async fetchEvents(): Promise<void> { async fetchEvents(): Promise<void> {
// Don't pass courseId and categoryId, we'll filter them locally. // Don't pass courseId and categoryId, we'll filter them locally.
const result = await AddonCalendar.getUpcomingEvents(); 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. // Merge the online events with offline data.
this.events = this.mergeEvents(); this.events = this.mergeEvents();
// Filter events by course. // Filter events by course.

View File

@ -672,7 +672,7 @@ class AddonCalendarDaySlidesItemsManagerSource extends CoreSwipeSlidesDynamicIte
try { try {
// Don't pass courseId and categoryId, we'll filter them locally. // 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()); 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) { } catch (error) {
// Allow navigating to non-cached days in offline (behave as if using emergency cache). // Allow navigating to non-cached days in offline (behave as if using emergency cache).
if (CoreNetwork.isOnline()) { if (CoreNetwork.isOnline()) {

View File

@ -197,7 +197,7 @@ export class AddonCalendarEventPage implements OnInit, OnDestroy {
// Get the event data. // Get the event data.
if (this.eventId >= 0) { if (this.eventId >= 0) {
const event = await AddonCalendar.getEventById(this.eventId); const event = await AddonCalendar.getEventById(this.eventId);
this.event = AddonCalendarHelper.formatEventData(event); this.event = await AddonCalendarHelper.formatEventData(event);
} }
try { try {

View File

@ -37,6 +37,7 @@ import { AddonCalendarOfflineEventDBRecord } from './database/calendar-offline';
import { CoreCategoryData } from '@features/courses/services/courses'; import { CoreCategoryData } from '@features/courses/services/courses';
import { CoreTimeUtils } from '@services/utils/time'; import { CoreTimeUtils } from '@services/utils/time';
import { CoreReminders, CoreRemindersService } from '@features/reminders/services/reminders'; import { CoreReminders, CoreRemindersService } from '@features/reminders/services/reminders';
import { CoreCourseModuleDelegate } from '@features/course/services/module-delegate';
/** /**
* Context levels enumeration. * Context levels enumeration.
@ -164,9 +165,9 @@ export class AddonCalendarHelperProvider {
* @param event Event to format. * @param event Event to format.
* @returns The formatted event to display. * @returns The formatted event to display.
*/ */
formatEventData( async formatEventData(
event: AddonCalendarEvent | AddonCalendarEventBase | AddonCalendarGetEventsEvent, event: AddonCalendarEvent | AddonCalendarEventBase | AddonCalendarGetEventsEvent,
): AddonCalendarEventToDisplay { ): Promise<AddonCalendarEventToDisplay> {
const eventFormatted: AddonCalendarEventToDisplay = { const eventFormatted: AddonCalendarEventToDisplay = {
...event, ...event,
@ -182,7 +183,10 @@ export class AddonCalendarHelperProvider {
}; };
if (event.modulename) { 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.moduleIcon = eventFormatted.eventIcon;
eventFormatted.iconTitle = CoreCourse.translateModuleName(event.modulename); eventFormatted.iconTitle = CoreCourse.translateModuleName(event.modulename);
} }

View File

@ -1809,6 +1809,7 @@ export type AddonCalendarEventBase = {
key: string; // Key. key: string; // Key.
component: string; // Component. component: string; // Component.
alttext: string; // Alttext. alttext: string; // Alttext.
iconurl?: string; // @since 4.2. Icon image url.
}; };
category?: { category?: {
id: number; // Id. id: number; // Id.

View File

@ -52,7 +52,7 @@ export class AddonModLabelModuleHandlerService extends CoreModuleHandlerBase imp
module.description = ''; module.description = '';
return { return {
icon: '', icon: this.getIconSrc(),
title, title,
a11yTitle: '', a11yTitle: '',
class: 'addon-mod-label-handler', class: 'addon-mod-label-handler',
@ -74,5 +74,12 @@ export class AddonModLabelModuleHandlerService extends CoreModuleHandlerBase imp
return true; return true;
} }
/**
* @inheritdoc
*/
getIconSrc(): string {
return '';
}
} }
export const AddonModLabelModuleHandler = makeSingleton(AddonModLabelModuleHandlerService); export const AddonModLabelModuleHandler = makeSingleton(AddonModLabelModuleHandlerService);

View File

@ -58,10 +58,6 @@ export class AddonModLtiModuleHandlerService extends CoreModuleHandlerBase imple
): Promise<CoreCourseModuleHandlerData> { ): Promise<CoreCourseModuleHandlerData> {
const data = await super.getData(module, courseId, sectionId, forCoursePage); const data = await super.getData(module, courseId, sectionId, forCoursePage);
data.showDownloadButton = false; data.showDownloadButton = false;
// Handle custom icons.
data.icon = module.modicon;
data.buttons = [{ data.buttons = [{
icon: 'fas-external-link-alt', icon: 'fas-external-link-alt',
label: 'addon.mod_lti.launchactivity', label: 'addon.mod_lti.launchactivity',
@ -83,6 +79,13 @@ export class AddonModLtiModuleHandlerService extends CoreModuleHandlerBase imple
return AddonModLtiIndexComponent; 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); export const AddonModLtiModuleHandler = makeSingleton(AddonModLtiModuleHandlerService);

View File

@ -41,7 +41,7 @@ export class CoreModuleHandlerBase implements Partial<CoreCourseModuleHandler> {
forCoursePage?: boolean, // eslint-disable-line @typescript-eslint/no-unused-vars forCoursePage?: boolean, // eslint-disable-line @typescript-eslint/no-unused-vars
): Promise<CoreCourseModuleHandlerData> | CoreCourseModuleHandlerData { ): Promise<CoreCourseModuleHandlerData> | CoreCourseModuleHandlerData {
return { return {
icon: CoreCourse.getModuleIconSrc(module.modname, module.modicon), icon: this.getIconSrc(module, module.modicon),
title: module.name, title: module.name,
class: 'addon-mod_' + module.modname + '-handler', class: 'addon-mod_' + module.modname + '-handler',
showDownloadButton: true, showDownloadButton: true,
@ -78,4 +78,15 @@ export class CoreModuleHandlerBase implements Partial<CoreCourseModuleHandler> {
await CoreNavigator.navigateToSitePath(this.pageName + routeParams, options); 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);
}
} }

View File

@ -82,9 +82,10 @@ export interface CoreCourseModuleHandler extends CoreDelegateHandler {
* Get the icon src for the module. * Get the icon src for the module.
* *
* @param module Module to get the icon from. * @param module Module to get the icon from.
* @param modicon The mod icon string.
* @returns The icon src. * @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. * 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. * @returns Promise resolved with the icon src.
*/ */
async getModuleIconSrc(modname: string, modicon?: string, module?: CoreCourseModuleData): Promise<string> { 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) ?? '';
} }
/** /**

View File

@ -206,7 +206,7 @@ export class CoreGradesCoursePage implements AfterViewInit, OnDestroy {
*/ */
private async fetchGrades(): Promise<void> { private async fetchGrades(): Promise<void> {
const table = await CoreGrades.getCourseGradesTable(this.courseId, this.userId); 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.title = formattedTable.rows[0]?.gradeitem ?? Translate.instant('core.grades.grades');
this.columns = formattedTable.columns; this.columns = formattedTable.columns;

View File

@ -37,6 +37,7 @@ import { makeSingleton, Translate } from '@singletons';
import { CoreError } from '@classes/errors/error'; import { CoreError } from '@classes/errors/error';
import { CoreCourseHelper } from '@features/course/services/course-helper'; import { CoreCourseHelper } from '@features/course/services/course-helper';
import { CoreAppProvider } from '@services/app'; import { CoreAppProvider } from '@services/app';
import { CoreCourseModuleDelegate } from '@features/course/services/module-delegate';
export const GRADES_PAGE_NAME = 'grades'; export const GRADES_PAGE_NAME = 'grades';
@ -73,7 +74,7 @@ export class CoreGradesHelperProvider {
let content = String(column.content); let content = String(column.content);
if (name == 'itemname') { if (name == 'itemname') {
this.setRowIconAndType(row, content); await this.setRowIconAndType(row, content);
row.link = this.getModuleLink(content); row.link = this.getModuleLink(content);
row.rowclass += column.class.indexOf('hidden') >= 0 ? ' hidden' : ''; 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. * @param useLegacyLayout Whether to use the layout before 4.1.
* @returns Formatted row object. * @returns Formatted row object.
*/ */
protected formatGradeRowForTable(tableRow: CoreGradesTableRow, useLegacyLayout: boolean): CoreGradesFormattedTableRow { protected async formatGradeRowForTable(
tableRow: CoreGradesTableRow,
useLegacyLayout: boolean,
): Promise<CoreGradesFormattedTableRow> {
const row: CoreGradesFormattedTableRow = {}; const row: CoreGradesFormattedTableRow = {};
if (!useLegacyLayout && 'leader' in tableRow) { if (!useLegacyLayout && 'leader' in tableRow) {
@ -132,7 +136,7 @@ export class CoreGradesHelperProvider {
row.colspan = itemNameColumn.colspan; row.colspan = itemNameColumn.colspan;
row.rowspan = tableRow.leader?.rowspan || 1; row.rowspan = tableRow.leader?.rowspan || 1;
this.setRowIconAndType(row, content); await this.setRowIconAndType(row, content);
this.setRowStyleClasses(row, itemNameColumn.class); this.setRowStyleClasses(row, itemNameColumn.class);
row.rowclass += itemNameColumn.class.indexOf('hidden') >= 0 ? ' hidden' : ''; row.rowclass += itemNameColumn.class.indexOf('hidden') >= 0 ? ' hidden' : '';
row.rowclass += itemNameColumn.class.indexOf('dimmed_text') >= 0 ? ' dimmed_text' : ''; 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. * @param table JSON object representing a table with data.
* @returns Formatted HTML table. * @returns Formatted HTML table.
*/ */
formatGradesTable(table: CoreGradesTable): CoreGradesFormattedTable { async formatGradesTable(table: CoreGradesTable): Promise<CoreGradesFormattedTable> {
const maxDepth = table.maxdepth; const maxDepth = table.maxdepth;
const formatted: CoreGradesFormattedTable = { const formatted: CoreGradesFormattedTable = {
columns: [], columns: [],
@ -223,7 +227,7 @@ export class CoreGradesHelperProvider {
feedback: false, feedback: false,
contributiontocoursetotal: false, contributiontocoursetotal: false,
}; };
formatted.rows = this.formatGradesTableRows(table.tabledata); formatted.rows = await this.formatGradesTableRows(table.tabledata);
// Get a row with some info. // Get a row with some info.
let normalRow = formatted.rows.find( let normalRow = formatted.rows.find(
@ -261,9 +265,9 @@ export class CoreGradesHelperProvider {
* @param rows Unformatted rows. * @param rows Unformatted rows.
* @returns Formatted rows. * @returns Formatted rows.
*/ */
protected formatGradesTableRows(rows: CoreGradesTableRow[]): CoreGradesFormattedTableRow[] { protected async formatGradesTableRows(rows: CoreGradesTableRow[]): Promise<CoreGradesFormattedTableRow[]> {
const useLegacyLayout = !CoreSites.getRequiredCurrentSite().isVersionGreaterEqualThan('4.1'); 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) { if (!useLegacyLayout) {
for (let index = 0; index < formattedRows.length - 1; index++) { for (let index = 0; index < formattedRows.length - 1; index++) {
@ -652,7 +656,7 @@ export class CoreGradesHelperProvider {
* @param row Row. * @param row Row.
* @param text Row content. * @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', '/'); text = text.replace('%2F', '/').replace('%2f', '/');
if (text.indexOf('/agg_mean') > -1) { if (text.indexOf('/agg_mean') > -1) {
row.itemtype = 'agg_mean'; row.itemtype = 'agg_mean';
@ -684,7 +688,7 @@ export class CoreGradesHelperProvider {
row.itemtype = 'mod'; row.itemtype = 'mod';
row.itemmodule = module[1]; row.itemmodule = module[1];
row.iconAlt = CoreCourse.translateModuleName(row.itemmodule) || ''; row.iconAlt = CoreCourse.translateModuleName(row.itemmodule) || '';
row.image = CoreCourse.getModuleIconSrc( row.image = await CoreCourseModuleDelegate.getModuleIconSrc(
module[1], module[1],
CoreDomUtils.convertToElement(text).querySelector('img')?.getAttribute('src') ?? undefined, CoreDomUtils.convertToElement(text).querySelector('img')?.getAttribute('src') ?? undefined,
); );