diff --git a/scripts/langindex.json b/scripts/langindex.json index f4b615cda..3c7cda084 100644 --- a/scripts/langindex.json +++ b/scripts/langindex.json @@ -27,6 +27,8 @@ "addon.block_recentlyaccesseditems.noitems": "block_recentlyaccesseditems", "addon.block_recentlyaccesseditems.pluginname": "block_recentlyaccesseditems", "addon.block_sitemainmenu.pluginname": "block_site_main_menu", + "addon.block_starredcourses.nocourses": "block_starredcourses", + "addon.block_starredcourses.pluginname": "block_starredcourses", "addon.block_timeline.duedate": "block_timeline", "addon.block_timeline.next30days": "block_timeline", "addon.block_timeline.next3months": "block_timeline", diff --git a/src/addon/block/starredcourses/components/components.module.ts b/src/addon/block/starredcourses/components/components.module.ts new file mode 100644 index 000000000..f24aff329 --- /dev/null +++ b/src/addon/block/starredcourses/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 { AddonBlockStarredCoursesComponent } from './starredcourses/starredcourses'; +import { CoreComponentsModule } from '@components/components.module'; +import { CoreDirectivesModule } from '@directives/directives.module'; +import { CoreCoursesComponentsModule } from '@core/courses/components/components.module'; + +@NgModule({ + declarations: [ + AddonBlockStarredCoursesComponent + ], + imports: [ + CommonModule, + IonicModule, + TranslateModule.forChild(), + CoreComponentsModule, + CoreDirectivesModule, + CoreCoursesComponentsModule + ], + providers: [ + ], + exports: [ + AddonBlockStarredCoursesComponent + ], + entryComponents: [ + AddonBlockStarredCoursesComponent + ] +}) +export class AddonBlockStarredCoursesComponentsModule {} diff --git a/src/addon/block/starredcourses/components/starredcourses/addon-block-starredcourses.html b/src/addon/block/starredcourses/components/starredcourses/addon-block-starredcourses.html new file mode 100644 index 000000000..121eb1ffa --- /dev/null +++ b/src/addon/block/starredcourses/components/starredcourses/addon-block-starredcourses.html @@ -0,0 +1,21 @@ + +

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

+
+ + {{prefetchCoursesData.badge}} + +
+
+ + + + + + + + + + + diff --git a/src/addon/block/starredcourses/components/starredcourses/starredcourses.ts b/src/addon/block/starredcourses/components/starredcourses/starredcourses.ts new file mode 100644 index 000000000..66d2945bb --- /dev/null +++ b/src/addon/block/starredcourses/components/starredcourses/starredcourses.ts @@ -0,0 +1,160 @@ +// (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 { CoreSitesProvider } from '@providers/sites'; +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 starred courses block. + */ +@Component({ + selector: 'addon-block-starredcourses', + templateUrl: 'addon-block-starredcourses.html' +}) +export class AddonBlockStarredCoursesComponent extends CoreBlockBaseComponent implements OnInit, OnDestroy { + courses = []; + prefetchCoursesData = { + icon: '', + badge: '' + }; + downloadAllCoursesEnabled: boolean; + + protected prefetchIconsInitialized = false; + protected isDestroyed; + protected updateSiteObserver; + protected coursesObserver; + protected courseIds = []; + protected fetchContentDefaultError = 'Error getting starred 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, + private sitesProvider: CoreSitesProvider) { + + super(injector, 'AddonBlockStarredCoursesComponent'); + } + + /** + * 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(); + } + }, this.sitesProvider.getCurrentSiteId()); + + this.coursesObserver = this.eventsProvider.on(CoreCoursesProvider.EVENT_MY_COURSES_UPDATED, () => { + this.refreshContent(); + }, this.sitesProvider.getCurrentSiteId()); + + 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. + * + * @return {Promise} Promise resolved when done. + */ + protected fetchContent(): Promise { + return this.coursesHelper.getUserCoursesWithOptions('timemodified', 0, 'isfavourite').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.coursesObserver && this.coursesObserver.off(); + this.updateSiteObserver && this.updateSiteObserver.off(); + } +} diff --git a/src/addon/block/starredcourses/lang/en.json b/src/addon/block/starredcourses/lang/en.json new file mode 100644 index 000000000..cc3dd03c7 --- /dev/null +++ b/src/addon/block/starredcourses/lang/en.json @@ -0,0 +1,4 @@ +{ + "nocourses": "No starred courses", + "pluginname": "Starred courses" +} diff --git a/src/addon/block/starredcourses/providers/block-handler.ts b/src/addon/block/starredcourses/providers/block-handler.ts new file mode 100644 index 000000000..7675876b1 --- /dev/null +++ b/src/addon/block/starredcourses/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 { AddonBlockStarredCoursesComponent } from '../components/starredcourses/starredcourses'; +import { CoreBlockBaseHandler } from '@core/block/classes/base-block-handler'; + +/** + * Block handler. + */ +@Injectable() +export class AddonBlockStarredCoursesHandler extends CoreBlockBaseHandler { + name = 'AddonBlockStarredCourses'; + blockName = 'starredcourses'; + + 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.starredcourses.pluginname', + class: 'addon-block-starredcourses', + component: AddonBlockStarredCoursesComponent + }; + } +} diff --git a/src/addon/block/starredcourses/starredcourses.module.ts b/src/addon/block/starredcourses/starredcourses.module.ts new file mode 100644 index 000000000..3d363ec79 --- /dev/null +++ b/src/addon/block/starredcourses/starredcourses.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 { AddonBlockStarredCoursesComponentsModule } from './components/components.module'; +import { AddonBlockStarredCoursesHandler } from './providers/block-handler'; + +@NgModule({ + declarations: [ + ], + imports: [ + IonicModule, + CoreComponentsModule, + AddonBlockStarredCoursesComponentsModule, + TranslateModule.forChild() + ], + providers: [ + AddonBlockStarredCoursesHandler + ] +}) +export class AddonBlockStarredCoursesModule { + constructor(blockDelegate: CoreBlockDelegate, blockHandler: AddonBlockStarredCoursesHandler) { + blockDelegate.registerHandler(blockHandler); + } +} diff --git a/src/app/app.module.ts b/src/app/app.module.ts index ca4cd023b..9f9a9dd27 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -90,6 +90,7 @@ import { AddonBlockSiteMainMenuModule } from '@addon/block/sitemainmenu/sitemain import { AddonBlockTimelineModule } from '@addon/block/timeline/timeline.module'; import { AddonBlockRecentlyAccessedCoursesModule } from '@addon/block/recentlyaccessedcourses/recentlyaccessedcourses.module'; import { AddonBlockRecentlyAccessedItemsModule } from '@addon/block/recentlyaccesseditems/recentlyaccesseditems.module'; +import { AddonBlockStarredCoursesModule } from '@addon/block/starredcourses/starredcourses.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'; @@ -206,6 +207,7 @@ export const CORE_PROVIDERS: any[] = [ AddonBlockTimelineModule, AddonBlockRecentlyAccessedCoursesModule, AddonBlockRecentlyAccessedItemsModule, + AddonBlockStarredCoursesModule, AddonModAssignModule, AddonModBookModule, AddonModChatModule, diff --git a/src/assets/lang/en.json b/src/assets/lang/en.json index 1b99a5363..f9a96b969 100644 --- a/src/assets/lang/en.json +++ b/src/assets/lang/en.json @@ -27,6 +27,8 @@ "addon.block_recentlyaccesseditems.noitems": "No recent items", "addon.block_recentlyaccesseditems.pluginname": "Recently accessed items", "addon.block_sitemainmenu.pluginname": "Main menu", + "addon.block_starredcourses.nocourses": "No starred courses", + "addon.block_starredcourses.pluginname": "Starred courses", "addon.block_timeline.duedate": "Due date", "addon.block_timeline.next30days": "Next 30 days", "addon.block_timeline.next3months": "Next 3 months", diff --git a/src/core/courses/providers/helper.ts b/src/core/courses/providers/helper.ts index d0d0177d5..1fa52078f 100644 --- a/src/core/courses/providers/helper.ts +++ b/src/core/courses/providers/helper.ts @@ -89,9 +89,10 @@ export class CoreCoursesHelperProvider { * * @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. + * @param {string} [filter] Filter using some field. * @return {Promise} Courses filled with options. */ - getUserCoursesWithOptions(sort: string = 'fullname', slice: number = 0): Promise { + getUserCoursesWithOptions(sort: string = 'fullname', slice: number = 0, filter?: string): Promise { return this.coursesProvider.getUserCourses().then((courses) => { const promises = [], courseIds = courses.map((course) => { @@ -114,6 +115,17 @@ export class CoreCoursesHelperProvider { if (courses.length <= 0) { return []; } + + switch (filter) { + case 'isfavourite': + courses = courses.filter((course) => { + return !!course.isfavourite; + }); + break; + default: + // Filter not implemented. + } + switch (sort) { case 'fullname': courses.sort((a, b) => { @@ -128,9 +140,15 @@ export class CoreCoursesHelperProvider { return b.lastaccess - b.lastaccess; }); break; + case 'timemodified': + courses.sort((a, b) => { + return b.timemodified - b.timemodified; + }); + break; default: // Sort not implemented. Do not sort. } + courses = slice > 0 ? courses.slice(0, slice) : courses; // Fetch course completion status if needed.