From 89030c9bc0038455a3c9bc1902838bf0ffa0ec2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Mon, 8 Nov 2021 16:19:08 +0100 Subject: [PATCH] MOBILE-3833 blocks: Performance improvements --- .../recentlyaccessedcourses.ts | 136 +++++++++--------- .../starredcourses/starredcourses.ts | 100 +++++++------ 2 files changed, 118 insertions(+), 118 deletions(-) diff --git a/src/addons/block/recentlyaccessedcourses/components/recentlyaccessedcourses/recentlyaccessedcourses.ts b/src/addons/block/recentlyaccessedcourses/components/recentlyaccessedcourses/recentlyaccessedcourses.ts index de9d5a8c9..4d31b6553 100644 --- a/src/addons/block/recentlyaccessedcourses/components/recentlyaccessedcourses/recentlyaccessedcourses.ts +++ b/src/addons/block/recentlyaccessedcourses/components/recentlyaccessedcourses/recentlyaccessedcourses.ts @@ -15,7 +15,12 @@ import { Component, OnInit, OnDestroy, Input, OnChanges, SimpleChange } from '@angular/core'; import { CoreEventObserver, CoreEvents } from '@singletons/events'; 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 { CoreCourseHelper, CorePrefetchStatusInfo } from '@features/course/services/course-helper'; import { CoreCourseOptionsDelegate } from '@features/course/services/course-options-delegate'; @@ -35,7 +40,7 @@ export class AddonBlockRecentlyAccessedCoursesComponent extends CoreBlockBaseCom @Input() downloadEnabled = false; - courses: CoreCourseSearchedDataWithExtraInfoAndOptions[] = []; + courses: (Omit & CoreCourseSearchedDataWithExtraInfoAndOptions)[] = []; prefetchCoursesData: CorePrefetchStatusInfo = { icon: '', statusTranslatable: 'core.loading', @@ -52,7 +57,6 @@ export class AddonBlockRecentlyAccessedCoursesComponent extends CoreBlockBaseCom protected isDestroyed = false; protected coursesObserver?: CoreEventObserver; protected updateSiteObserver?: CoreEventObserver; - protected courseIds = []; protected fetchContentDefaultError = 'Error getting recent courses data.'; constructor() { @@ -60,7 +64,7 @@ export class AddonBlockRecentlyAccessedCoursesComponent extends CoreBlockBaseCom } /** - * Component being initialized. + * @inheritdoc */ async ngOnInit(): Promise { // Generate unique id for scroll element. @@ -82,12 +86,8 @@ export class AddonBlockRecentlyAccessedCoursesComponent extends CoreBlockBaseCom this.coursesObserver = CoreEvents.on( CoreCoursesProvider.EVENT_MY_COURSES_UPDATED, (data) => { - - if (this.shouldRefreshOnUpdatedEvent(data)) { - this.refreshCourseList(); - } + this.refreshCourseList(data); }, - CoreSites.getCurrentSiteId(), ); @@ -95,7 +95,7 @@ export class AddonBlockRecentlyAccessedCoursesComponent extends CoreBlockBaseCom } /** - * Detect changes on input properties. + * @inheritdoc */ ngOnChanges(changes: {[name: string]: SimpleChange}): void { if (changes.downloadEnabled && !changes.downloadEnabled.previousValue && this.downloadEnabled && this.loaded) { @@ -105,21 +105,35 @@ export class AddonBlockRecentlyAccessedCoursesComponent extends CoreBlockBaseCom } /** - * Perform the invalidate content function. - * - * @return Resolved when done. + * @inheritdoc */ protected async invalidateContent(): Promise { + 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 { const promises: Promise[] = []; + // Invalidate course completion data. promises.push(CoreCourses.invalidateRecentCourses().finally(() => - // Invalidate course completion data. - CoreUtils.allPromises(this.courseIds.map((courseId) => + CoreUtils.allPromises(courseIds.map((courseId) => AddonCourseCompletion.invalidateCourseCompletion(courseId))))); - promises.push(CoreCourseOptionsDelegate.clearAndInvalidateCoursesOptions()); - if (this.courseIds.length > 0) { - promises.push(CoreCourses.invalidateCoursesByField('ids', this.courseIds.join(','))); + if (courseIds.length == 1) { + promises.push(CoreCourseOptionsDelegate.clearAndInvalidateCoursesOptions(courseIds[0])); + } else { + promises.push(CoreCourseOptionsDelegate.clearAndInvalidateCoursesOptions()); + } + if (courseIds.length > 0) { + promises.push(CoreCourses.invalidateCoursesByField('ids', courseIds.join(','))); } await CoreUtils.allPromises(promises).finally(() => { @@ -128,9 +142,7 @@ export class AddonBlockRecentlyAccessedCoursesComponent extends CoreBlockBaseCom } /** - * Fetch the courses for recent courses. - * - * @return Promise resolved when done. + * @inheritdoc */ protected async fetchContent(): Promise { 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); // Get the courses using getCoursesByField to get more info about each course. - const courses: CoreCourseSearchedDataWithExtraInfoAndOptions[] = await CoreCourses.getCoursesByField( - 'ids', - courseIds.join(','), - ); + const courses = await CoreCourses.getCoursesByField('ids', courseIds.join(',')); - // Sort them in the original order. - courses.sort((courseA, courseB) => courseIds.indexOf(courseA.id) - courseIds.indexOf(courseB.id)); + this.courses = recentCourses.map((recentCourse) => { + const course = courses.find((course) => recentCourse.id == course.id); + + return Object.assign(recentCourse, course); + }); // Get course options and extra info. const options = await CoreCourses.getCoursesAdminAndNavOptions(courseIds); - courses.forEach((course) => { + this.courses.forEach((course) => { course.navOptions = options.navOptions[course.id]; course.admOptions = options.admOptions[course.id]; @@ -161,24 +173,9 @@ export class AddonBlockRecentlyAccessedCoursesComponent extends CoreBlockBaseCom await CoreCoursesHelper.loadCoursesColorAndImage(courses); - this.courses = courses; - this.initPrefetchCoursesIcons(); } - /** - * Refresh the list of courses. - * - * @return Promise resolved when done. - */ - protected async refreshCourseList(): Promise { - CoreEvents.trigger(CoreCoursesProvider.EVENT_MY_COURSES_REFRESHED); - - await CoreUtils.ignoreErrors(CoreCourses.invalidateRecentCourses()); - - await this.loadContent(true); - } - /** * 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. - * @return Whether to refresh. + * @return Promise resolved when done. */ - protected shouldRefreshOnUpdatedEvent(data: CoreCoursesMyCoursesUpdatedEventData): boolean { + protected async refreshCourseList(data: CoreCoursesMyCoursesUpdatedEventData): Promise { if (data.action == CoreCoursesProvider.ACTION_ENROL) { // Always update if user enrolled in a course. - return true; + return await this.refreshContent(); } - if (data.action == CoreCoursesProvider.ACTION_VIEW && data.courseId != CoreSites.getCurrentSiteHomeId() && - this.courses[0] && data.courseId != this.courses[0].id) { - // Update list if user viewed a course that isn't the most recent one and isn't site home. - return true; + const courseIndex = this.courses.findIndex((course) => course.id == data.courseId); + const course = this.courses[courseIndex]; + if (data.action == CoreCoursesProvider.ACTION_VIEW && data.courseId != CoreSites.getCurrentSiteHomeId()) { + if (!course) { + // Not found, use WS update. + return await this.refreshContent(); + } + + // Place at the begining. + this.courses.splice(courseIndex, 1); + this.courses.unshift(course); + + await this.invalidateCourses([course.id]); } - if (data.action == CoreCoursesProvider.ACTION_STATE_CHANGED && data.state == CoreCoursesProvider.STATE_FAVOURITE && - data.courseId && this.hasCourse(data.courseId)) { - // Update list if a visible course is now favourite or unfavourite. - return true; + if (data.action == CoreCoursesProvider.ACTION_STATE_CHANGED && + data.state == CoreCoursesProvider.STATE_FAVOURITE && course) { + course.isfavourite = !!data.value; + await this.invalidateCourses([course.id]); + + this.initPrefetchCoursesIcons(); } - - return false; - } - - /** - * 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 { this.isDestroyed = true; diff --git a/src/addons/block/starredcourses/components/starredcourses/starredcourses.ts b/src/addons/block/starredcourses/components/starredcourses/starredcourses.ts index b95d8084d..766c695c7 100644 --- a/src/addons/block/starredcourses/components/starredcourses/starredcourses.ts +++ b/src/addons/block/starredcourses/components/starredcourses/starredcourses.ts @@ -52,7 +52,6 @@ export class AddonBlockStarredCoursesComponent extends CoreBlockBaseComponent im protected isDestroyed = false; protected coursesObserver?: CoreEventObserver; protected updateSiteObserver?: CoreEventObserver; - protected courseIds: number[] = []; protected fetchContentDefaultError = 'Error getting starred courses data.'; constructor() { @@ -60,7 +59,7 @@ export class AddonBlockStarredCoursesComponent extends CoreBlockBaseComponent im } /** - * Component being initialized. + * @inheritdoc */ async ngOnInit(): Promise { // Generate unique id for scroll element. @@ -76,17 +75,12 @@ export class AddonBlockStarredCoursesComponent extends CoreBlockBaseComponent im this.updateSiteObserver = CoreEvents.on(CoreEvents.SITE_UPDATED, () => { this.downloadCourseEnabled = !CoreCourses.isDownloadCourseDisabledInSite(); this.downloadCoursesEnabled = !CoreCourses.isDownloadCoursesDisabledInSite(); - }, CoreSites.getCurrentSiteId()); this.coursesObserver = CoreEvents.on( CoreCoursesProvider.EVENT_MY_COURSES_UPDATED, (data) => { - - if (this.shouldRefreshOnUpdatedEvent(data)) { - this.refreshCourseList(); - } - this.refreshContent(); + this.refreshCourseList(data); }, CoreSites.getCurrentSiteId(), @@ -96,7 +90,7 @@ export class AddonBlockStarredCoursesComponent extends CoreBlockBaseComponent im } /** - * Detect changes on input properties. + * @inheritdoc */ ngOnChanges(changes: {[name: string]: SimpleChange}): void { 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. - * - * @return Resolved when done. + * @inheritdoc */ protected async invalidateContent(): Promise { + 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 { const promises: Promise[] = []; + // Invalidate course completion data. promises.push(CoreCourses.invalidateUserCourses().finally(() => - // Invalidate course completion data. - CoreUtils.allPromises(this.courseIds.map((courseId) => + CoreUtils.allPromises(courseIds.map((courseId) => AddonCourseCompletion.invalidateCourseCompletion(courseId))))); - promises.push(CoreCourseOptionsDelegate.clearAndInvalidateCoursesOptions()); - if (this.courseIds.length > 0) { - promises.push(CoreCourses.invalidateCoursesByField('ids', this.courseIds.join(','))); + if (courseIds.length == 1) { + promises.push(CoreCourseOptionsDelegate.clearAndInvalidateCoursesOptions(courseIds[0])); + } else { + promises.push(CoreCourseOptionsDelegate.clearAndInvalidateCoursesOptions()); + } + if (courseIds.length > 0) { + promises.push(CoreCourses.invalidateCoursesByField('ids', courseIds.join(','))); } await CoreUtils.allPromises(promises).finally(() => { @@ -129,54 +137,54 @@ export class AddonBlockStarredCoursesComponent extends CoreBlockBaseComponent im } /** - * Fetch the courses. - * - * @return Promise resolved when done. + * @inheritdoc */ protected async fetchContent(): Promise { const showCategories = this.block.configsRecord && this.block.configsRecord.displaycategories && 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.initPrefetchCoursesIcons(); } /** - * Refresh the list of courses. - * - * @return Promise resolved when done. - */ - protected async refreshCourseList(): Promise { - 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. + * Refresh course list based on a EVENT_MY_COURSES_UPDATED event. * * @param data Event data. - * @return Whether to refresh. + * @return Promise resolved when done. */ - protected shouldRefreshOnUpdatedEvent(data: CoreCoursesMyCoursesUpdatedEventData): boolean { + protected async refreshCourseList(data: CoreCoursesMyCoursesUpdatedEventData): Promise { if (data.action == CoreCoursesProvider.ACTION_ENROL) { // Always update if user enrolled in a course. // 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) { - // Update list when making a course favourite or not. - return true; - } + const courseIndex = this.courses.findIndex((course) => course.id == data.courseId); + 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 { this.isDestroyed = true;