diff --git a/scripts/langindex.json b/scripts/langindex.json index 118e7bc6a..945d384fe 100644 --- a/scripts/langindex.json +++ b/scripts/langindex.json @@ -22,7 +22,8 @@ "addon.block_myoverview.past": "block_myoverview", "addon.block_myoverview.pluginname": "block_myoverview", "addon.block_myoverview.title": "block_myoverview", - "addon.block_recentcourses.nocourses": "moodle", + "addon.block_recentlyaccessedcourses.nocourses": "block_recentlyaccessedcourses", + "addon.block_recentlyaccessedcourses.pluginname": "block_recentlyaccessedcourses", "addon.block_sitemainmenu.pluginname": "block_site_main_menu", "addon.block_timeline.duedate": "block_timeline", "addon.block_timeline.next30days": "block_timeline", diff --git a/src/addon/block/myoverview/components/myoverview/addon-block-myoverview.html b/src/addon/block/myoverview/components/myoverview/addon-block-myoverview.html index 40e1441c6..c5d5d1521 100644 --- a/src/addon/block/myoverview/components/myoverview/addon-block-myoverview.html +++ b/src/addon/block/myoverview/components/myoverview/addon-block-myoverview.html @@ -24,7 +24,7 @@ - {{ 'addon.block_myoverview.title' | translate }} + {{ 'addon.block_myoverview.title' | translate }} {{ 'addon.block_myoverview.lastaccessed' | translate }} diff --git a/src/addon/block/myoverview/components/myoverview/myoverview.ts b/src/addon/block/myoverview/components/myoverview/myoverview.ts index 47bee2553..b628deccd 100644 --- a/src/addon/block/myoverview/components/myoverview/myoverview.ts +++ b/src/addon/block/myoverview/components/myoverview/myoverview.ts @@ -45,7 +45,7 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem hidden: [] }; selectedFilter = 'inprogress'; - sort = 'title'; + sort = 'fullname'; currentSite: any; downloadAllCoursesEnabled: boolean; filteredCourses: any[]; @@ -103,7 +103,7 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem this.sort = value; })); promises.push(this.currentSite.getLocalSiteConfig('AddonBlockMyOverviewFilter', this.selectedFilter).then((value) => { - this.selectedFilter = value; + this.selectedFilter = typeof this.courses[value] == 'undefined' ? 'inprogress' : value; })); Promise.all(promises).finally(() => { @@ -145,26 +145,10 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem * @return {Promise} Promise resolved when done. */ protected fetchContent(): Promise { - return this.coursesHelper.getUserCoursesWithOptions().then((courses) => { - // Fetch course completion status. - return Promise.all(courses.map((course) => { - if (typeof course.enablecompletion != 'undefined' && course.enablecompletion == 0) { - // Completion is disabled for this course, there is no need to fetch the completion status. - return Promise.resolve(course); - } - - return this.courseCompletionProvider.getCompletion(course.id).catch(() => { - // Ignore error, maybe course compleiton is disabled or user ha no permission. - }).then((completion) => { - course.completed = completion && completion.completed; - - return course; - }); - })); - }).then((courses) => { + return this.coursesHelper.getUserCoursesWithOptions(this.sort).then((courses) => { this.showSortFilter = courses.length > 0 && typeof courses[0].lastaccess != 'undefined'; - this.sortCourses(courses); + this.initCourseFilters(courses); this.courses.filter = ''; this.showFilter = false; @@ -210,22 +194,9 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem this.prefetchIconsInitialized = true; Object.keys(this.prefetchCoursesData).forEach((filter) => { - if (!this.courses[filter] || this.courses[filter].length < 2) { - // Not enough courses. - this.prefetchCoursesData[filter].icon = ''; - - return; - } - - this.courseHelper.determineCoursesStatus(this.courses[filter]).then((status) => { - let icon = this.courseHelper.getCourseStatusIconAndTitleFromStatus(status).icon; - if (icon == 'spinner') { - // It seems all courses are being downloaded, show a download button instead. - icon = 'cloud-download'; - } - this.prefetchCoursesData[filter].icon = icon; + this.courseHelper.initPrefetchCoursesIcons(this.courses[filter], this.prefetchCoursesData[filter]).then((prefetch) => { + this.prefetchCoursesData[filter] = prefetch; }); - }); } @@ -236,23 +207,13 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem */ prefetchCourses(): Promise { const selected = this.selectedFilter, - selectedData = this.prefetchCoursesData[selected], - initialIcon = selectedData.icon; + initialIcon = this.prefetchCoursesData[selected].icon; - selectedData.icon = 'spinner'; - selectedData.badge = ''; - - return this.courseHelper.confirmAndPrefetchCourses(this.courses[this.selectedFilter], (progress) => { - selectedData.badge = progress.count + ' / ' + progress.total; - }).then(() => { - selectedData.icon = 'refresh'; - }).catch((error) => { + return this.courseHelper.prefetchCourses(this.courses[selected], this.prefetchCoursesData[selected]).catch((error) => { if (!this.isDestroyed) { this.domUtils.showErrorModalDefault(error, 'core.course.errordownloadingcourse', true); - selectedData.icon = initialIcon; + this.prefetchCoursesData[selected].icon = initialIcon; } - }).finally(() => { - selectedData.badge = ''; }); } @@ -265,25 +226,11 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem } /** - * Sort and init courses filters. - * @param {any[]} courses Courses to sort. + * Init courses filters. + * + * @param {any[]} courses Courses to filter. */ - sortCourses(courses: any[]): void { - if (this.showSortFilter) { - if (this.sort == 'lastaccess') { - courses.sort((a, b) => { - return b.lastaccess - a.lastaccess; - }); - } else if (this.sort == 'title') { - courses.sort((a, b) => { - const compareA = a.fullname.toLowerCase(), - compareB = b.fullname.toLowerCase(); - - return compareA.localeCompare(compareB); - }); - } - } - + initCourseFilters(courses: any[]): void { this.courses.all = []; this.courses.past = []; this.courses.inprogress = []; @@ -314,6 +261,8 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem } } }); + + this.filteredCourses = this.courses[this.selectedFilter]; } /** @@ -321,7 +270,24 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem */ switchSort(): void { this.currentSite.setLocalSiteConfig('AddonBlockMyOverviewSort', this.sort); - this.sortCourses(this.courses.all); + const courses = this.courses.all.concat(this.courses.hidden); + + if (this.showSortFilter) { + if (this.sort == 'lastaccess') { + courses.sort((a, b) => { + return b.lastaccess - a.lastaccess; + }); + } else if (this.sort == 'fullname') { + courses.sort((a, b) => { + const compareA = a.fullname.toLowerCase(), + compareB = b.fullname.toLowerCase(); + + return compareA.localeCompare(compareB); + }); + } + } + + this.initCourseFilters(courses); } /** diff --git a/src/addon/block/recentlyaccessedcourses/components/components.module.ts b/src/addon/block/recentlyaccessedcourses/components/components.module.ts new file mode 100644 index 000000000..dde98101c --- /dev/null +++ b/src/addon/block/recentlyaccessedcourses/components/components.module.ts @@ -0,0 +1,45 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { IonicModule } from 'ionic-angular'; +import { TranslateModule } from '@ngx-translate/core'; +import { AddonBlockRecentlyAccessedCoursesComponent } from './recentlyaccessedcourses/recentlyaccessedcourses'; +import { CoreComponentsModule } from '@components/components.module'; +import { CoreDirectivesModule } from '@directives/directives.module'; +import { CoreCoursesComponentsModule } from '@core/courses/components/components.module'; + +@NgModule({ + declarations: [ + AddonBlockRecentlyAccessedCoursesComponent + ], + imports: [ + CommonModule, + IonicModule, + TranslateModule.forChild(), + CoreComponentsModule, + CoreDirectivesModule, + CoreCoursesComponentsModule + ], + providers: [ + ], + exports: [ + AddonBlockRecentlyAccessedCoursesComponent + ], + entryComponents: [ + AddonBlockRecentlyAccessedCoursesComponent + ] +}) +export class AddonBlockRecentlyAccessedCoursesComponentsModule {} diff --git a/src/addon/block/recentlyaccessedcourses/components/recentlyaccessedcourses/addon-block-recentlyaccessedcourses.html b/src/addon/block/recentlyaccessedcourses/components/recentlyaccessedcourses/addon-block-recentlyaccessedcourses.html new file mode 100644 index 000000000..18e176104 --- /dev/null +++ b/src/addon/block/recentlyaccessedcourses/components/recentlyaccessedcourses/addon-block-recentlyaccessedcourses.html @@ -0,0 +1,21 @@ + +

{{ 'addon.block_recentlyaccessedcourses.pluginname' | translate }}

+
+ + {{prefetchCoursesData.badge}} + +
+
+ + + + + + + + + + + diff --git a/src/addon/block/recentlyaccessedcourses/components/recentlyaccessedcourses/recentlyaccessedcourses.ts b/src/addon/block/recentlyaccessedcourses/components/recentlyaccessedcourses/recentlyaccessedcourses.ts new file mode 100644 index 000000000..9f1b3d64d --- /dev/null +++ b/src/addon/block/recentlyaccessedcourses/components/recentlyaccessedcourses/recentlyaccessedcourses.ts @@ -0,0 +1,152 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Component, OnInit, OnDestroy, Injector } from '@angular/core'; +import { CoreEventsProvider } from '@providers/events'; +import { CoreUtilsProvider } from '@providers/utils/utils'; +import { CoreCoursesProvider } from '@core/courses/providers/courses'; +import { CoreCoursesHelperProvider } from '@core/courses/providers/helper'; +import { CoreCourseHelperProvider } from '@core/course/providers/helper'; +import { CoreCourseOptionsDelegate } from '@core/course/providers/options-delegate'; +import { AddonCourseCompletionProvider } from '@addon/coursecompletion/providers/coursecompletion'; +import { CoreBlockBaseComponent } from '@core/block/classes/base-block-component'; + +/** + * Component to render a recent courses block. + */ +@Component({ + selector: 'addon-block-recentlyaccessedcourses', + templateUrl: 'addon-block-recentlyaccessedcourses.html' +}) +export class AddonBlockRecentlyAccessedCoursesComponent extends CoreBlockBaseComponent implements OnInit, OnDestroy { + courses = []; + prefetchCoursesData = { + icon: '', + badge: '' + }; + downloadAllCoursesEnabled: boolean; + + protected prefetchIconsInitialized = false; + protected isDestroyed; + protected updateSiteObserver; + protected courseIds = []; + protected fetchContentDefaultError = 'Error getting recent courses data.'; + + constructor(injector: Injector, private coursesProvider: CoreCoursesProvider, + private courseCompletionProvider: AddonCourseCompletionProvider, private eventsProvider: CoreEventsProvider, + private courseHelper: CoreCourseHelperProvider, private utils: CoreUtilsProvider, + private courseOptionsDelegate: CoreCourseOptionsDelegate, private coursesHelper: CoreCoursesHelperProvider) { + + super(injector, 'AddonBlockRecentlyAccessedCoursesComponent'); + } + + /** + * Component being initialized. + */ + ngOnInit(): void { + this.downloadAllCoursesEnabled = !this.coursesProvider.isDownloadCoursesDisabledInSite(); + + // Refresh the enabled flags if site is updated. + this.updateSiteObserver = this.eventsProvider.on(CoreEventsProvider.SITE_UPDATED, () => { + const wasEnabled = this.downloadAllCoursesEnabled; + + this.downloadAllCoursesEnabled = !this.coursesProvider.isDownloadCoursesDisabledInSite(); + + if (!wasEnabled && this.downloadAllCoursesEnabled && this.loaded) { + // Download all courses is enabled now, initialize it. + this.initPrefetchCoursesIcons(); + } + }); + + super.ngOnInit(); + } + + /** + * Perform the invalidate content function. + * + * @return {Promise} Resolved when done. + */ + protected invalidateContent(): Promise { + const promises = []; + + promises.push(this.coursesProvider.invalidateUserCourses().finally(() => { + // Invalidate course completion data. + return this.utils.allPromises(this.courseIds.map((courseId) => { + return this.courseCompletionProvider.invalidateCourseCompletion(courseId); + })); + })); + + promises.push(this.courseOptionsDelegate.clearAndInvalidateCoursesOptions()); + if (this.courseIds.length > 0) { + promises.push(this.coursesProvider.invalidateCoursesByField('ids', this.courseIds.join(','))); + } + + return this.utils.allPromises(promises).finally(() => { + this.prefetchIconsInitialized = false; + }); + } + + /** + * Fetch the courses for recent courses. + * + * @return {Promise} Promise resolved when done. + */ + protected fetchContent(): Promise { + return this.coursesHelper.getUserCoursesWithOptions('lastaccess', 10).then((courses) => { + this.courses = courses; + + this.initPrefetchCoursesIcons(); + }); + } + + /** + * Initialize the prefetch icon for selected courses. + */ + protected initPrefetchCoursesIcons(): void { + if (this.prefetchIconsInitialized || !this.downloadAllCoursesEnabled) { + // Already initialized. + return; + } + + this.prefetchIconsInitialized = true; + + this.courseHelper.initPrefetchCoursesIcons(this.courses, this.prefetchCoursesData).then((prefetch) => { + this.prefetchCoursesData = prefetch; + }); + } + + /** + * Prefetch all the shown courses. + * + * @return {Promise} Promise resolved when done. + */ + prefetchCourses(): Promise { + const initialIcon = this.prefetchCoursesData.icon; + + return this.courseHelper.prefetchCourses(this.courses, this.prefetchCoursesData).catch((error) => { + if (!this.isDestroyed) { + this.domUtils.showErrorModalDefault(error, 'core.course.errordownloadingcourse', true); + this.prefetchCoursesData.icon = initialIcon; + } + }); + } + + /** + * Component being destroyed. + */ + ngOnDestroy(): void { + this.isDestroyed = true; + this.updateSiteObserver && this.updateSiteObserver.off(); + } +} diff --git a/src/addon/block/recentlyaccessedcourses/lang/en.json b/src/addon/block/recentlyaccessedcourses/lang/en.json new file mode 100644 index 000000000..e87006df7 --- /dev/null +++ b/src/addon/block/recentlyaccessedcourses/lang/en.json @@ -0,0 +1,4 @@ +{ + "nocourses": "No recent courses", + "pluginname": "Recently accessed courses" +} diff --git a/src/addon/block/recentlyaccessedcourses/providers/block-handler.ts b/src/addon/block/recentlyaccessedcourses/providers/block-handler.ts new file mode 100644 index 000000000..a1117bd5d --- /dev/null +++ b/src/addon/block/recentlyaccessedcourses/providers/block-handler.ts @@ -0,0 +1,50 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Injectable, Injector } from '@angular/core'; +import { CoreBlockHandlerData } from '@core/block/providers/delegate'; +import { AddonBlockRecentlyAccessedCoursesComponent } from '../components/recentlyaccessedcourses/recentlyaccessedcourses'; +import { CoreBlockBaseHandler } from '@core/block/classes/base-block-handler'; + +/** + * Block handler. + */ +@Injectable() +export class AddonBlockRecentlyAccessedCoursesHandler extends CoreBlockBaseHandler { + name = 'AddonBlockRecentlyAccessedCourses'; + blockName = 'recentlyaccessedcourses'; + + constructor() { + super(); + } + + /** + * Returns the data needed to render the block. + * + * @param {Injector} injector Injector. + * @param {any} block The block to render. + * @param {string} contextLevel The context where the block will be used. + * @param {number} instanceId The instance ID associated with the context level. + * @return {CoreBlockHandlerData|Promise} Data or promise resolved with the data. + */ + getDisplayData?(injector: Injector, block: any, contextLevel: string, instanceId: number) + : CoreBlockHandlerData | Promise { + + return { + title: 'addon.block_recentlyaccessedcourses.pluginname', + class: 'addon-block-recentlyaccessedcourses', + component: AddonBlockRecentlyAccessedCoursesComponent + }; + } +} diff --git a/src/addon/block/recentlyaccessedcourses/recentlyaccessedcourses.module.ts b/src/addon/block/recentlyaccessedcourses/recentlyaccessedcourses.module.ts new file mode 100644 index 000000000..99a9ad3fd --- /dev/null +++ b/src/addon/block/recentlyaccessedcourses/recentlyaccessedcourses.module.ts @@ -0,0 +1,40 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { NgModule } from '@angular/core'; +import { IonicModule } from 'ionic-angular'; +import { TranslateModule } from '@ngx-translate/core'; +import { CoreComponentsModule } from '@components/components.module'; +import { CoreBlockDelegate } from '@core/block/providers/delegate'; +import { AddonBlockRecentlyAccessedCoursesComponentsModule } from './components/components.module'; +import { AddonBlockRecentlyAccessedCoursesHandler } from './providers/block-handler'; + +@NgModule({ + declarations: [ + ], + imports: [ + IonicModule, + CoreComponentsModule, + AddonBlockRecentlyAccessedCoursesComponentsModule, + TranslateModule.forChild() + ], + providers: [ + AddonBlockRecentlyAccessedCoursesHandler + ] +}) +export class AddonBlockRecentlyAccessedCoursesModule { + constructor(blockDelegate: CoreBlockDelegate, blockHandler: AddonBlockRecentlyAccessedCoursesHandler) { + blockDelegate.registerHandler(blockHandler); + } +} diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 04988829d..d54c8dfe3 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -88,6 +88,7 @@ import { AddonBlockActivityModulesModule } from '@addon/block/activitymodules/ac import { AddonBlockMyOverviewModule } from '@addon/block/myoverview/myoverview.module'; import { AddonBlockSiteMainMenuModule } from '@addon/block/sitemainmenu/sitemainmenu.module'; import { AddonBlockTimelineModule } from '@addon/block/timeline/timeline.module'; +import { AddonBlockRecentlyAccessedCoursesModule } from '@addon/block/recentlyaccessedcourses/recentlyaccessedcourses.module'; import { AddonModAssignModule } from '@addon/mod/assign/assign.module'; import { AddonModBookModule } from '@addon/mod/book/book.module'; import { AddonModChatModule } from '@addon/mod/chat/chat.module'; @@ -202,6 +203,7 @@ export const CORE_PROVIDERS: any[] = [ AddonBlockMyOverviewModule, AddonBlockSiteMainMenuModule, AddonBlockTimelineModule, + AddonBlockRecentlyAccessedCoursesModule, AddonModAssignModule, AddonModBookModule, AddonModChatModule, diff --git a/src/assets/lang/en.json b/src/assets/lang/en.json index cfa5bf287..7449c5012 100644 --- a/src/assets/lang/en.json +++ b/src/assets/lang/en.json @@ -22,6 +22,8 @@ "addon.block_myoverview.past": "Past", "addon.block_myoverview.pluginname": "Course Overview", "addon.block_myoverview.title": "Title", + "addon.block_recentlyaccessedcourses.nocourses": "No recent courses", + "addon.block_recentlyaccessedcourses.pluginname": "Recently accessed courses", "addon.block_sitemainmenu.pluginname": "Main menu", "addon.block_timeline.duedate": "Due date", "addon.block_timeline.next30days": "Next 30 days", diff --git a/src/core/course/providers/helper.ts b/src/core/course/providers/helper.ts index 968887a3e..c48df70ad 100644 --- a/src/core/course/providers/helper.ts +++ b/src/core/course/providers/helper.ts @@ -738,6 +738,53 @@ export class CoreCourseHelperProvider { }); } + /** + * Initialize the prefetch icon for selected courses. + * + * @param {any[]} courses Courses array to get info from. + * @param {any} prefetch Prefetch information. + * @return {Promise} Resolved with the prefetch information updated when done. + */ + initPrefetchCoursesIcons(courses: any[], prefetch: any): Promise { + if (!courses || courses.length < 2) { + // Not enough courses. + prefetch.icon = ''; + + return Promise.resolve(prefetch); + } + + return this.determineCoursesStatus(courses).then((status) => { + let icon = this.getCourseStatusIconAndTitleFromStatus(status).icon; + if (icon == 'spinner') { + // It seems all courses are being downloaded, show a download button instead. + icon = 'cloud-download'; + } + prefetch.icon = icon; + + return prefetch; + }); + } + + /** + * Prefetch all the courses in the array. + * + * @param {any[]} courses Courses array to prefetch. + * @param {any} prefetch Prefetch information to be updated. + * @return {Promise} Promise resolved when done. + */ + prefetchCourses(courses: any[], prefetch: any): Promise { + prefetch.icon = 'spinner'; + prefetch.badge = ''; + + return this.confirmAndPrefetchCourses(courses, (progress) => { + prefetch.badge = progress.count + ' / ' + progress.total; + }).then(() => { + prefetch.icon = 'refresh'; + }).finally(() => { + prefetch.badge = ''; + }); + } + /** * Get a course download promise (if any). * diff --git a/src/core/courses/providers/helper.ts b/src/core/courses/providers/helper.ts index 87d168465..77673b760 100644 --- a/src/core/courses/providers/helper.ts +++ b/src/core/courses/providers/helper.ts @@ -15,6 +15,7 @@ import { Injectable } from '@angular/core'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreCoursesProvider } from './courses'; +import { AddonCourseCompletionProvider } from '@addon/coursecompletion/providers/coursecompletion'; /** * Helper to gather some common courses functions. @@ -22,7 +23,8 @@ import { CoreCoursesProvider } from './courses'; @Injectable() export class CoreCoursesHelperProvider { - constructor(private coursesProvider: CoreCoursesProvider, private utils: CoreUtilsProvider) { } + constructor(private coursesProvider: CoreCoursesProvider, private utils: CoreUtilsProvider, + private courseCompletionProvider: AddonCourseCompletionProvider) { } /** * Given a course object returned by core_enrol_get_users_courses and another one returned by core_course_get_courses_by_field, @@ -85,9 +87,11 @@ export class CoreCoursesHelperProvider { /** * Get user courses with admin and nav options. * - * @return {Promise} Promise resolved when done. + * @param {string} [sort=fullname] Sort courses after get them. If sort is not defined it won't be sorted. + * @param {number} [slice=0] Slice results to get the X first one. If slice > 0 it will be done after sorting. + * @return {Promise} Courses filled with options. */ - getUserCoursesWithOptions(): Promise { + getUserCoursesWithOptions(sort: string = 'fullname', slice: number = 0): Promise { return this.coursesProvider.getUserCourses().then((courses) => { const promises = [], courseIds = courses.map((course) => { @@ -107,12 +111,43 @@ export class CoreCoursesHelperProvider { promises.push(this.loadCoursesExtraInfo(courses)); return Promise.all(promises).then(() => { - return courses.sort((a, b) => { - const compareA = a.fullname.toLowerCase(), - compareB = b.fullname.toLowerCase(); + if (courses.length <= 0) { + return []; + } + switch (sort) { + case 'fullname': + courses.sort((a, b) => { + const compareA = a.fullname.toLowerCase(), + compareB = b.fullname.toLowerCase(); - return compareA.localeCompare(compareB); - }); + return compareA.localeCompare(compareB); + }); + break; + case 'lastaccess': + courses.sort((a, b) => { + return b.lastaccess - b.lastaccess; + }); + break; + default: + // Sort not implemented. Do not sort. + } + courses = slice > 0 ? courses.slice(0, slice) : courses; + + // Fetch course completion status. + return Promise.all(courses.map((course) => { + if (typeof course.enablecompletion != 'undefined' && course.enablecompletion == 0) { + // Completion is disabled for this course, there is no need to fetch the completion status. + return Promise.resolve(course); + } + + return this.courseCompletionProvider.getCompletion(course.id).catch(() => { + // Ignore error, maybe course completion is disabled or user has no permission. + }).then((completion) => { + course.completed = completion && completion.completed; + + return course; + }); + })); }); }); }