2
0
Fork 0

MOBILE-3833 blocks: Performance improvements

main
Pau Ferrer Ocaña 2021-11-08 16:19:08 +01:00
parent e7f8b554df
commit 89030c9bc0
2 changed files with 118 additions and 118 deletions

View File

@ -15,7 +15,12 @@
import { Component, OnInit, OnDestroy, Input, OnChanges, SimpleChange } from '@angular/core'; import { Component, OnInit, OnDestroy, Input, OnChanges, SimpleChange } from '@angular/core';
import { CoreEventObserver, CoreEvents } from '@singletons/events'; import { CoreEventObserver, CoreEvents } from '@singletons/events';
import { CoreSites } from '@services/sites'; import { CoreSites } from '@services/sites';
import { CoreCoursesProvider, CoreCoursesMyCoursesUpdatedEventData, CoreCourses } from '@features/courses/services/courses'; import {
CoreCoursesProvider,
CoreCoursesMyCoursesUpdatedEventData,
CoreCourses,
CoreCourseSummaryData,
} from '@features/courses/services/courses';
import { CoreCourseSearchedDataWithExtraInfoAndOptions, CoreCoursesHelper } from '@features/courses/services/courses-helper'; import { CoreCourseSearchedDataWithExtraInfoAndOptions, CoreCoursesHelper } from '@features/courses/services/courses-helper';
import { CoreCourseHelper, CorePrefetchStatusInfo } from '@features/course/services/course-helper'; import { CoreCourseHelper, CorePrefetchStatusInfo } from '@features/course/services/course-helper';
import { CoreCourseOptionsDelegate } from '@features/course/services/course-options-delegate'; import { CoreCourseOptionsDelegate } from '@features/course/services/course-options-delegate';
@ -35,7 +40,7 @@ export class AddonBlockRecentlyAccessedCoursesComponent extends CoreBlockBaseCom
@Input() downloadEnabled = false; @Input() downloadEnabled = false;
courses: CoreCourseSearchedDataWithExtraInfoAndOptions[] = []; courses: (Omit<CoreCourseSummaryData, 'visible'> & CoreCourseSearchedDataWithExtraInfoAndOptions)[] = [];
prefetchCoursesData: CorePrefetchStatusInfo = { prefetchCoursesData: CorePrefetchStatusInfo = {
icon: '', icon: '',
statusTranslatable: 'core.loading', statusTranslatable: 'core.loading',
@ -52,7 +57,6 @@ export class AddonBlockRecentlyAccessedCoursesComponent extends CoreBlockBaseCom
protected isDestroyed = false; protected isDestroyed = false;
protected coursesObserver?: CoreEventObserver; protected coursesObserver?: CoreEventObserver;
protected updateSiteObserver?: CoreEventObserver; protected updateSiteObserver?: CoreEventObserver;
protected courseIds = [];
protected fetchContentDefaultError = 'Error getting recent courses data.'; protected fetchContentDefaultError = 'Error getting recent courses data.';
constructor() { constructor() {
@ -60,7 +64,7 @@ export class AddonBlockRecentlyAccessedCoursesComponent extends CoreBlockBaseCom
} }
/** /**
* Component being initialized. * @inheritdoc
*/ */
async ngOnInit(): Promise<void> { async ngOnInit(): Promise<void> {
// Generate unique id for scroll element. // Generate unique id for scroll element.
@ -82,12 +86,8 @@ export class AddonBlockRecentlyAccessedCoursesComponent extends CoreBlockBaseCom
this.coursesObserver = CoreEvents.on( this.coursesObserver = CoreEvents.on(
CoreCoursesProvider.EVENT_MY_COURSES_UPDATED, CoreCoursesProvider.EVENT_MY_COURSES_UPDATED,
(data) => { (data) => {
this.refreshCourseList(data);
if (this.shouldRefreshOnUpdatedEvent(data)) {
this.refreshCourseList();
}
}, },
CoreSites.getCurrentSiteId(), CoreSites.getCurrentSiteId(),
); );
@ -95,7 +95,7 @@ export class AddonBlockRecentlyAccessedCoursesComponent extends CoreBlockBaseCom
} }
/** /**
* Detect changes on input properties. * @inheritdoc
*/ */
ngOnChanges(changes: {[name: string]: SimpleChange}): void { ngOnChanges(changes: {[name: string]: SimpleChange}): void {
if (changes.downloadEnabled && !changes.downloadEnabled.previousValue && this.downloadEnabled && this.loaded) { if (changes.downloadEnabled && !changes.downloadEnabled.previousValue && this.downloadEnabled && this.loaded) {
@ -105,21 +105,35 @@ export class AddonBlockRecentlyAccessedCoursesComponent extends CoreBlockBaseCom
} }
/** /**
* Perform the invalidate content function. * @inheritdoc
*
* @return Resolved when done.
*/ */
protected async invalidateContent(): Promise<void> { protected async invalidateContent(): Promise<void> {
const courseIds = this.courses.map((course) => course.id);
await this.invalidateCourses(courseIds);
}
/**
* Helper function to invalidate only selected courses.
*
* @param courseIds Course Id array.
* @return Promise resolved when done.
*/
protected async invalidateCourses(courseIds: number[]): Promise<void> {
const promises: Promise<void>[] = []; const promises: Promise<void>[] = [];
promises.push(CoreCourses.invalidateRecentCourses().finally(() =>
// Invalidate course completion data. // Invalidate course completion data.
CoreUtils.allPromises(this.courseIds.map((courseId) => promises.push(CoreCourses.invalidateRecentCourses().finally(() =>
CoreUtils.allPromises(courseIds.map((courseId) =>
AddonCourseCompletion.invalidateCourseCompletion(courseId))))); AddonCourseCompletion.invalidateCourseCompletion(courseId)))));
if (courseIds.length == 1) {
promises.push(CoreCourseOptionsDelegate.clearAndInvalidateCoursesOptions(courseIds[0]));
} else {
promises.push(CoreCourseOptionsDelegate.clearAndInvalidateCoursesOptions()); promises.push(CoreCourseOptionsDelegate.clearAndInvalidateCoursesOptions());
if (this.courseIds.length > 0) { }
promises.push(CoreCourses.invalidateCoursesByField('ids', this.courseIds.join(','))); if (courseIds.length > 0) {
promises.push(CoreCourses.invalidateCoursesByField('ids', courseIds.join(',')));
} }
await CoreUtils.allPromises(promises).finally(() => { await CoreUtils.allPromises(promises).finally(() => {
@ -128,9 +142,7 @@ export class AddonBlockRecentlyAccessedCoursesComponent extends CoreBlockBaseCom
} }
/** /**
* Fetch the courses for recent courses. * @inheritdoc
*
* @return Promise resolved when done.
*/ */
protected async fetchContent(): Promise<void> { protected async fetchContent(): Promise<void> {
const showCategories = this.block.configsRecord && this.block.configsRecord.displaycategories && const showCategories = this.block.configsRecord && this.block.configsRecord.displaycategories &&
@ -140,17 +152,17 @@ export class AddonBlockRecentlyAccessedCoursesComponent extends CoreBlockBaseCom
const courseIds = recentCourses.map((course) => course.id); const courseIds = recentCourses.map((course) => course.id);
// Get the courses using getCoursesByField to get more info about each course. // Get the courses using getCoursesByField to get more info about each course.
const courses: CoreCourseSearchedDataWithExtraInfoAndOptions[] = await CoreCourses.getCoursesByField( const courses = await CoreCourses.getCoursesByField('ids', courseIds.join(','));
'ids',
courseIds.join(','),
);
// Sort them in the original order. this.courses = recentCourses.map((recentCourse) => {
courses.sort((courseA, courseB) => courseIds.indexOf(courseA.id) - courseIds.indexOf(courseB.id)); const course = courses.find((course) => recentCourse.id == course.id);
return Object.assign(recentCourse, course);
});
// Get course options and extra info. // Get course options and extra info.
const options = await CoreCourses.getCoursesAdminAndNavOptions(courseIds); const options = await CoreCourses.getCoursesAdminAndNavOptions(courseIds);
courses.forEach((course) => { this.courses.forEach((course) => {
course.navOptions = options.navOptions[course.id]; course.navOptions = options.navOptions[course.id];
course.admOptions = options.admOptions[course.id]; course.admOptions = options.admOptions[course.id];
@ -161,24 +173,9 @@ export class AddonBlockRecentlyAccessedCoursesComponent extends CoreBlockBaseCom
await CoreCoursesHelper.loadCoursesColorAndImage(courses); await CoreCoursesHelper.loadCoursesColorAndImage(courses);
this.courses = courses;
this.initPrefetchCoursesIcons(); this.initPrefetchCoursesIcons();
} }
/**
* Refresh the list of courses.
*
* @return Promise resolved when done.
*/
protected async refreshCourseList(): Promise<void> {
CoreEvents.trigger(CoreCoursesProvider.EVENT_MY_COURSES_REFRESHED);
await CoreUtils.ignoreErrors(CoreCourses.invalidateRecentCourses());
await this.loadContent(true);
}
/** /**
* Initialize the prefetch icon for selected courses. * Initialize the prefetch icon for selected courses.
*/ */
@ -194,44 +191,39 @@ export class AddonBlockRecentlyAccessedCoursesComponent extends CoreBlockBaseCom
} }
/** /**
* Whether list should be refreshed based on a EVENT_MY_COURSES_UPDATED event. * Refresh course list based on a EVENT_MY_COURSES_UPDATED event.
* *
* @param data Event data. * @param data Event data.
* @return Whether to refresh. * @return Promise resolved when done.
*/ */
protected shouldRefreshOnUpdatedEvent(data: CoreCoursesMyCoursesUpdatedEventData): boolean { protected async refreshCourseList(data: CoreCoursesMyCoursesUpdatedEventData): Promise<void> {
if (data.action == CoreCoursesProvider.ACTION_ENROL) { if (data.action == CoreCoursesProvider.ACTION_ENROL) {
// Always update if user enrolled in a course. // Always update if user enrolled in a course.
return true; return await this.refreshContent();
} }
if (data.action == CoreCoursesProvider.ACTION_VIEW && data.courseId != CoreSites.getCurrentSiteHomeId() && const courseIndex = this.courses.findIndex((course) => course.id == data.courseId);
this.courses[0] && data.courseId != this.courses[0].id) { const course = this.courses[courseIndex];
// Update list if user viewed a course that isn't the most recent one and isn't site home. if (data.action == CoreCoursesProvider.ACTION_VIEW && data.courseId != CoreSites.getCurrentSiteHomeId()) {
return true; if (!course) {
// Not found, use WS update.
return await this.refreshContent();
} }
if (data.action == CoreCoursesProvider.ACTION_STATE_CHANGED && data.state == CoreCoursesProvider.STATE_FAVOURITE && // Place at the begining.
data.courseId && this.hasCourse(data.courseId)) { this.courses.splice(courseIndex, 1);
// Update list if a visible course is now favourite or unfavourite. this.courses.unshift(course);
return true;
await this.invalidateCourses([course.id]);
} }
return false; if (data.action == CoreCoursesProvider.ACTION_STATE_CHANGED &&
} data.state == CoreCoursesProvider.STATE_FAVOURITE && course) {
course.isfavourite = !!data.value;
await this.invalidateCourses([course.id]);
/** this.initPrefetchCoursesIcons();
* Check if a certain course is in the list of courses.
*
* @param courseId Course ID to search.
* @return Whether it's in the list.
*/
protected hasCourse(courseId: number): boolean {
if (!this.courses) {
return false;
} }
return !!this.courses.find((course) => course.id == courseId);
} }
/** /**
@ -253,7 +245,7 @@ export class AddonBlockRecentlyAccessedCoursesComponent extends CoreBlockBaseCom
} }
/** /**
* Component being destroyed. * @inheritdoc
*/ */
ngOnDestroy(): void { ngOnDestroy(): void {
this.isDestroyed = true; this.isDestroyed = true;

View File

@ -52,7 +52,6 @@ export class AddonBlockStarredCoursesComponent extends CoreBlockBaseComponent im
protected isDestroyed = false; protected isDestroyed = false;
protected coursesObserver?: CoreEventObserver; protected coursesObserver?: CoreEventObserver;
protected updateSiteObserver?: CoreEventObserver; protected updateSiteObserver?: CoreEventObserver;
protected courseIds: number[] = [];
protected fetchContentDefaultError = 'Error getting starred courses data.'; protected fetchContentDefaultError = 'Error getting starred courses data.';
constructor() { constructor() {
@ -60,7 +59,7 @@ export class AddonBlockStarredCoursesComponent extends CoreBlockBaseComponent im
} }
/** /**
* Component being initialized. * @inheritdoc
*/ */
async ngOnInit(): Promise<void> { async ngOnInit(): Promise<void> {
// Generate unique id for scroll element. // Generate unique id for scroll element.
@ -76,17 +75,12 @@ export class AddonBlockStarredCoursesComponent extends CoreBlockBaseComponent im
this.updateSiteObserver = CoreEvents.on(CoreEvents.SITE_UPDATED, () => { this.updateSiteObserver = CoreEvents.on(CoreEvents.SITE_UPDATED, () => {
this.downloadCourseEnabled = !CoreCourses.isDownloadCourseDisabledInSite(); this.downloadCourseEnabled = !CoreCourses.isDownloadCourseDisabledInSite();
this.downloadCoursesEnabled = !CoreCourses.isDownloadCoursesDisabledInSite(); this.downloadCoursesEnabled = !CoreCourses.isDownloadCoursesDisabledInSite();
}, CoreSites.getCurrentSiteId()); }, CoreSites.getCurrentSiteId());
this.coursesObserver = CoreEvents.on( this.coursesObserver = CoreEvents.on(
CoreCoursesProvider.EVENT_MY_COURSES_UPDATED, CoreCoursesProvider.EVENT_MY_COURSES_UPDATED,
(data) => { (data) => {
this.refreshCourseList(data);
if (this.shouldRefreshOnUpdatedEvent(data)) {
this.refreshCourseList();
}
this.refreshContent();
}, },
CoreSites.getCurrentSiteId(), CoreSites.getCurrentSiteId(),
@ -96,7 +90,7 @@ export class AddonBlockStarredCoursesComponent extends CoreBlockBaseComponent im
} }
/** /**
* Detect changes on input properties. * @inheritdoc
*/ */
ngOnChanges(changes: {[name: string]: SimpleChange}): void { ngOnChanges(changes: {[name: string]: SimpleChange}): void {
if (changes.downloadEnabled && !changes.downloadEnabled.previousValue && this.downloadEnabled && this.loaded) { if (changes.downloadEnabled && !changes.downloadEnabled.previousValue && this.downloadEnabled && this.loaded) {
@ -106,21 +100,35 @@ export class AddonBlockStarredCoursesComponent extends CoreBlockBaseComponent im
} }
/** /**
* Perform the invalidate content function. * @inheritdoc
*
* @return Resolved when done.
*/ */
protected async invalidateContent(): Promise<void> { protected async invalidateContent(): Promise<void> {
const courseIds = this.courses.map((course) => course.id);
await this.invalidateCourses(courseIds);
}
/**
* Helper function to invalidate only selected courses.
*
* @param courseIds Course Id array.
* @return Promise resolved when done.
*/
protected async invalidateCourses(courseIds: number[]): Promise<void> {
const promises: Promise<void>[] = []; const promises: Promise<void>[] = [];
promises.push(CoreCourses.invalidateUserCourses().finally(() =>
// Invalidate course completion data. // Invalidate course completion data.
CoreUtils.allPromises(this.courseIds.map((courseId) => promises.push(CoreCourses.invalidateUserCourses().finally(() =>
CoreUtils.allPromises(courseIds.map((courseId) =>
AddonCourseCompletion.invalidateCourseCompletion(courseId))))); AddonCourseCompletion.invalidateCourseCompletion(courseId)))));
if (courseIds.length == 1) {
promises.push(CoreCourseOptionsDelegate.clearAndInvalidateCoursesOptions(courseIds[0]));
} else {
promises.push(CoreCourseOptionsDelegate.clearAndInvalidateCoursesOptions()); promises.push(CoreCourseOptionsDelegate.clearAndInvalidateCoursesOptions());
if (this.courseIds.length > 0) { }
promises.push(CoreCourses.invalidateCoursesByField('ids', this.courseIds.join(','))); if (courseIds.length > 0) {
promises.push(CoreCourses.invalidateCoursesByField('ids', courseIds.join(',')));
} }
await CoreUtils.allPromises(promises).finally(() => { await CoreUtils.allPromises(promises).finally(() => {
@ -129,54 +137,54 @@ export class AddonBlockStarredCoursesComponent extends CoreBlockBaseComponent im
} }
/** /**
* Fetch the courses. * @inheritdoc
*
* @return Promise resolved when done.
*/ */
protected async fetchContent(): Promise<void> { protected async fetchContent(): Promise<void> {
const showCategories = this.block.configsRecord && this.block.configsRecord.displaycategories && const showCategories = this.block.configsRecord && this.block.configsRecord.displaycategories &&
this.block.configsRecord.displaycategories.value == '1'; this.block.configsRecord.displaycategories.value == '1';
// @TODO: Sort won't coincide with website because timemodified is not informed.
this.courses = await CoreCoursesHelper.getUserCoursesWithOptions('timemodified', 0, 'isfavourite', showCategories); this.courses = await CoreCoursesHelper.getUserCoursesWithOptions('timemodified', 0, 'isfavourite', showCategories);
this.initPrefetchCoursesIcons(); this.initPrefetchCoursesIcons();
} }
/** /**
* Refresh the list of courses. * Refresh course list based on a EVENT_MY_COURSES_UPDATED event.
*
* @return Promise resolved when done.
*/
protected async refreshCourseList(): Promise<void> {
CoreEvents.trigger(CoreCoursesProvider.EVENT_MY_COURSES_REFRESHED);
try {
await CoreCourses.invalidateUserCourses();
} catch (error) {
// Ignore errors.
}
await this.loadContent(true);
}
/**
* Whether list should be refreshed based on a EVENT_MY_COURSES_UPDATED event.
* *
* @param data Event data. * @param data Event data.
* @return Whether to refresh. * @return Promise resolved when done.
*/ */
protected shouldRefreshOnUpdatedEvent(data: CoreCoursesMyCoursesUpdatedEventData): boolean { protected async refreshCourseList(data: CoreCoursesMyCoursesUpdatedEventData): Promise<void> {
if (data.action == CoreCoursesProvider.ACTION_ENROL) { if (data.action == CoreCoursesProvider.ACTION_ENROL) {
// Always update if user enrolled in a course. // Always update if user enrolled in a course.
// New courses shouldn't be favourite by default, but just in case. // New courses shouldn't be favourite by default, but just in case.
return true; return await this.refreshContent();
} }
if (data.action == CoreCoursesProvider.ACTION_STATE_CHANGED && data.state == CoreCoursesProvider.STATE_FAVOURITE) { if (data.action == CoreCoursesProvider.ACTION_STATE_CHANGED && data.state == CoreCoursesProvider.STATE_FAVOURITE) {
// Update list when making a course favourite or not. const courseIndex = this.courses.findIndex((course) => course.id == data.courseId);
return true; if (courseIndex < 0) {
// Not found, use WS update. Usually new favourite.
return await this.refreshContent();
} }
return false; const course = this.courses[courseIndex];
if (data.value === false) {
// Unfavourite, just remove.
this.courses.splice(courseIndex, 1);
} else {
// List is not synced, favourite course and place it at the begining.
course.isfavourite = !!data.value;
this.courses.splice(courseIndex, 1);
this.courses.unshift(course);
}
await this.invalidateCourses([course.id]);
this.initPrefetchCoursesIcons();
}
} }
/** /**
@ -212,7 +220,7 @@ export class AddonBlockStarredCoursesComponent extends CoreBlockBaseComponent im
} }
/** /**
* Component being destroyed. * @inheritdoc
*/ */
ngOnDestroy(): void { ngOnDestroy(): void {
this.isDestroyed = true; this.isDestroyed = true;