From 7e2f2dfd4ff9b82bcf9147b9ad3b58df16e63110 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Wed, 10 Oct 2018 16:16:41 +0200 Subject: [PATCH] MOBILE-2667 blocks: Move my overview and timeline to blocks --- scripts/langindex.json | 28 +- src/addon/block/classes/block-component.ts | 138 +++++ .../component/addon-block-myoverview.html | 42 ++ .../block/myoverview/component/myoverview.ts | 277 ++++++++++ src/addon/block/myoverview/lang/en.json | 9 + .../block/myoverview/myoverview.module.ts | 36 ++ .../events/addon-block-timeline-events.html} | 12 +- .../timeline/components/events/events.scss} | 0 .../timeline/components/events/events.ts} | 6 +- .../timeline/addon-block-timeline.html | 21 + .../timeline/components/timeline/timeline.ts | 172 ++++++ src/addon/block/timeline/lang/en.json | 9 + .../block/timeline/providers/timeline.ts} | 39 +- src/addon/block/timeline/timeline.module.ts | 47 ++ src/app/app.module.ts | 4 + src/assets/lang/en.json | 28 +- .../courses/components/components.module.ts | 7 +- src/core/courses/courses.module.ts | 14 +- src/core/courses/lang/en.json | 14 - .../courses/pages/dashboard/dashboard.html | 50 ++ .../dashboard.module.ts} | 12 +- .../dashboard.scss} | 2 +- src/core/courses/pages/dashboard/dashboard.ts | 125 +++++ .../pages/my-overview/my-overview.html | 109 ---- .../courses/pages/my-overview/my-overview.ts | 511 ------------------ ...k-handler.ts => dashboard-link-handler.ts} | 4 +- src/core/courses/providers/dashboard.ts | 64 +++ src/core/courses/providers/helper.ts | 35 ++ .../courses/providers/mainmenu-handler.ts | 14 +- .../sitehome/providers/mainmenu-handler.ts | 7 +- 30 files changed, 1102 insertions(+), 734 deletions(-) create mode 100644 src/addon/block/classes/block-component.ts create mode 100644 src/addon/block/myoverview/component/addon-block-myoverview.html create mode 100644 src/addon/block/myoverview/component/myoverview.ts create mode 100644 src/addon/block/myoverview/lang/en.json create mode 100644 src/addon/block/myoverview/myoverview.module.ts rename src/{core/courses/components/overview-events/core-courses-overview-events.html => addon/block/timeline/components/events/addon-block-timeline-events.html} (78%) rename src/{core/courses/components/overview-events/overview-events.scss => addon/block/timeline/components/events/events.scss} (100%) rename src/{core/courses/components/overview-events/overview-events.ts => addon/block/timeline/components/events/events.ts} (96%) create mode 100644 src/addon/block/timeline/components/timeline/addon-block-timeline.html create mode 100644 src/addon/block/timeline/components/timeline/timeline.ts create mode 100644 src/addon/block/timeline/lang/en.json rename src/{core/courses/providers/my-overview.ts => addon/block/timeline/providers/timeline.ts} (88%) create mode 100644 src/addon/block/timeline/timeline.module.ts create mode 100644 src/core/courses/pages/dashboard/dashboard.html rename src/core/courses/pages/{my-overview/my-overview.module.ts => dashboard/dashboard.module.ts} (74%) rename src/core/courses/pages/{my-overview/my-overview.scss => dashboard/dashboard.scss} (77%) create mode 100644 src/core/courses/pages/dashboard/dashboard.ts delete mode 100644 src/core/courses/pages/my-overview/my-overview.html delete mode 100644 src/core/courses/pages/my-overview/my-overview.ts rename src/core/courses/providers/{my-overview-link-handler.ts => dashboard-link-handler.ts} (91%) create mode 100644 src/core/courses/providers/dashboard.ts diff --git a/scripts/langindex.json b/scripts/langindex.json index ba9376e82..c83831ffe 100644 --- a/scripts/langindex.json +++ b/scripts/langindex.json @@ -10,6 +10,20 @@ "addon.badges.issuername": "badges", "addon.badges.nobadges": "badges", "addon.badges.recipientdetails": "badges", + "addon.block_myoverview.future": "block_myoverview", + "addon.block_myoverview.inprogress": "block_myoverview", + "addon.block_myoverview.morecourses": "block_myoverview", + "addon.block_myoverview.nocoursesfuture": "block_myoverview", + "addon.block_myoverview.nocoursesinprogress": "block_myoverview", + "addon.block_myoverview.nocoursespast": "block_myoverview", + "addon.block_myoverview.past": "block_myoverview", + "addon.block_timeline.next30days": "block_timeline", + "addon.block_timeline.next7days": "block_timeline", + "addon.block_timeline.nocoursesinprogress": "block_timeline", + "addon.block_timeline.noevents": "block_timeline", + "addon.block_timeline.recentlyoverdue": "local_moodlemobileapp", + "addon.block_timeline.sortbycourses": "block_timeline", + "addon.block_timeline.sortbydates": "block_timeline", "addon.calendar.calendar": "calendar", "addon.calendar.calendarevents": "local_moodlemobileapp", "addon.calendar.defaultnotificationtime": "local_moodlemobileapp", @@ -1135,34 +1149,20 @@ "core.courses.errorselfenrol": "local_moodlemobileapp", "core.courses.filtermycourses": "local_moodlemobileapp", "core.courses.frontpage": "admin", - "core.courses.future": "block_myoverview", - "core.courses.inprogress": "block_myoverview", - "core.courses.morecourses": "block_myoverview", "core.courses.mycourses": "moodle", - "core.courses.next30days": "block_timeline", - "core.courses.next7days": "block_timeline", "core.courses.nocourses": "my", - "core.courses.nocoursesfuture": "block_myoverview", - "core.courses.nocoursesinprogress": "block_myoverview", - "core.courses.nocoursesoverview": "moodle/nocourses", - "core.courses.nocoursespast": "block_myoverview", "core.courses.nocoursesyet": "moodle", - "core.courses.noevents": "block_timeline", "core.courses.nosearchresults": "wiki", "core.courses.notenroled": "completion", "core.courses.notenrollable": "local_moodlemobileapp", "core.courses.password": "local_moodlemobileapp", - "core.courses.past": "block_myoverview", "core.courses.paymentrequired": "moodle", "core.courses.paypalaccepted": "enrol_paypal", - "core.courses.recentlyoverdue": "local_moodlemobileapp", "core.courses.search": "moodle", "core.courses.searchcourses": "moodle", "core.courses.searchcoursesadvice": "local_moodlemobileapp", "core.courses.selfenrolment": "local_moodlemobileapp", "core.courses.sendpaymentbutton": "enrol_paypal", - "core.courses.sortbycourses": "block_timeline", - "core.courses.sortbydates": "block_timeline", "core.courses.timeline": "block_dashboard", "core.courses.totalcoursesearchresults": "local_moodlemobileapp", "core.currentdevice": "local_moodlemobileapp", diff --git a/src/addon/block/classes/block-component.ts b/src/addon/block/classes/block-component.ts new file mode 100644 index 000000000..11e685922 --- /dev/null +++ b/src/addon/block/classes/block-component.ts @@ -0,0 +1,138 @@ +// (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 { Injector, OnInit } from '@angular/core'; +import { CoreLoggerProvider } from '@providers/logger'; +import { CoreDomUtilsProvider } from '@providers/utils/dom'; + +/** + * Template class to easily create AddonBlockComponent of blocks. + */ +export class AddonBlockComponent implements OnInit { + loaded: boolean; // If the component has been loaded. + protected fetchContentDefaultError: string; // Default error to show when loading contents. + + protected domUtils: CoreDomUtilsProvider; + protected logger; + + constructor(injector: Injector, loggerName: string = 'CoreCourseModuleMainResourceComponent') { + this.domUtils = injector.get(CoreDomUtilsProvider); + const loggerProvider = injector.get(CoreLoggerProvider); + this.logger = loggerProvider.getInstance(loggerName); + } + /** + * Component being initialized. + */ + ngOnInit(): void { + this.loaded = false; + this.loadContent(); + } + + /** + * Refresh the data. + * + * @param {any} [refresher] Refresher. + * @param {Function} [done] Function to call when done. + * @param {boolean} [showErrors=false] If show errors to the user of hide them. + * @return {Promise} Promise resolved when done. + */ + doRefresh(refresher?: any, done?: () => void, showErrors: boolean = false): Promise { + if (this.loaded) { + return this.invalidateContent().catch(() => { + // Ignore errors. + }).then(() => { + return this.refreshContent(showErrors).finally(() => { + refresher && refresher.complete(); + done && done(); + }); + }); + } + + return Promise.resolve(); + } + + /** + * Perform the refresh content function. + * + * @param {boolean} [showErrors=false] Wether to show errors to the user or hide them. + * @return {Promise} Resolved when done. + */ + protected refreshContent(showErrors: boolean = false): Promise { + // Wrap the call in a try/catch so the workflow isn't interrupted if an error occurs. + let promise; + + try { + promise = this.invalidateContent(); + } catch (ex) { + // An error ocurred in the function, log the error and just resolve the promise so the workflow continues. + this.logger.error(ex); + + promise = Promise.resolve(); + } + + return promise.catch(() => { + // Ignore errors. + }).then(() => { + return this.loadContent(true, showErrors); + }); + } + + /** + * Perform the invalidate content function. + * + * @return {Promise} Resolved when done. + */ + protected invalidateContent(): Promise { + return Promise.resolve(); + } + + /** + * Loads the component contents and shows the corresponding error. + * + * @param {boolean} [refresh=false] Whether we're refreshing data. + * @param {boolean} [showErrors=false] Wether to show errors to the user or hide them. + * @return {Promise} Promise resolved when done. + */ + protected loadContent(refresh?: boolean, showErrors: boolean = false): Promise { + // Wrap the call in a try/catch so the workflow isn't interrupted if an error occurs. + let promise; + + try { + promise = this.fetchContent(refresh); + } catch (ex) { + // An error ocurred in the function, log the error and just resolve the promise so the workflow continues. + this.logger.error(ex); + + promise = Promise.resolve(); + } + + return promise.catch((error) => { + // Error getting data, fail. + this.domUtils.showErrorModalDefault(error, this.fetchContentDefaultError, true); + }).finally(() => { + this.loaded = true; + }); + } + + /** + * Download the component contents. + * + * @param {boolean} [refresh] Whether we're refreshing data. + * @return {Promise} Promise resolved when done. + */ + protected fetchContent(refresh?: boolean): Promise { + return Promise.resolve(); + } + +} diff --git a/src/addon/block/myoverview/component/addon-block-myoverview.html b/src/addon/block/myoverview/component/addon-block-myoverview.html new file mode 100644 index 000000000..b580b1b1a --- /dev/null +++ b/src/addon/block/myoverview/component/addon-block-myoverview.html @@ -0,0 +1,42 @@ + + + + + + + +
+ + {{ 'addon.block_myoverview.inprogress' | translate }} + {{ 'addon.block_myoverview.future' | translate }} + {{ 'addon.block_myoverview.past' | translate }} + + +
+ + {{prefetchCoursesData[selectedFilter].badge}} + +
+
+ + + + + + + + +
+ + + + + + + +
+
\ No newline at end of file diff --git a/src/addon/block/myoverview/component/myoverview.ts b/src/addon/block/myoverview/component/myoverview.ts new file mode 100644 index 000000000..92660870a --- /dev/null +++ b/src/addon/block/myoverview/component/myoverview.ts @@ -0,0 +1,277 @@ +// (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, ViewChild, Injector } from '@angular/core'; +import { Searchbar } from 'ionic-angular'; +import * as moment from 'moment'; +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 { AddonBlockComponent } from '../../classes/block-component'; + +/** + * Component to render a my overview block. + */ +@Component({ + selector: 'addon-block-myoverview', + templateUrl: 'addon-block-myoverview.html' +}) +export class AddonBlockMyOverviewComponent extends AddonBlockComponent implements OnInit, OnDestroy { + @ViewChild('searchbar') searchbar: Searchbar; + + courses = { + filter: '', + past: [], + inprogress: [], + future: [] + }; + selectedFilter = 'inprogress'; + downloadAllCoursesEnabled: boolean; + filteredCourses: any[]; + prefetchCoursesData = { + inprogress: {}, + past: {}, + future: {} + }; + showFilter = false; + + protected prefetchIconsInitialized = false; + protected isDestroyed; + protected updateSiteObserver; + protected courseIds = []; + protected fetchContentDefaultError = 'Error getting my overview 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, 'AddonBlockMyOverviewComponent'); + } + + /** + * 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. + 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 my overview. + * + * @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) => { + const today = moment().unix(); + + this.courses.past = []; + this.courses.inprogress = []; + this.courses.future = []; + + courses.forEach((course) => { + if ((course.enddate && course.enddate < today) || course.completed) { + // Courses that have already ended. + this.courses.past.push(course); + } else if (course.startdate > today) { + // Courses that have not started yet. + this.courses.future.push(course); + } else { + // Courses still in progress. + this.courses.inprogress.push(course); + } + }); + + this.courses.filter = ''; + this.showFilter = false; + this.filteredCourses = this.courses[this.selectedFilter]; + + this.initPrefetchCoursesIcons(); + }); + } + + /** + * The filter has changed. + * + * @param {any} Received Event. + */ + filterChanged(event: any): void { + const newValue = event.target.value && event.target.value.trim().toLowerCase(); + if (!newValue || !this.courses[this.selectedFilter]) { + this.filteredCourses = this.courses[this.selectedFilter]; + } else { + this.filteredCourses = this.courses[this.selectedFilter].filter((course) => { + return course.fullname.toLowerCase().indexOf(newValue) > -1; + }); + } + } + + /** + * Initialize the prefetch icon for selected courses. + */ + protected initPrefetchCoursesIcons(): void { + if (this.prefetchIconsInitialized || !this.downloadAllCoursesEnabled) { + // Already initialized. + return; + } + + 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; + }); + + }); + } + + /** + * Prefetch all the shown courses. + * + * @return {Promise} Promise resolved when done. + */ + prefetchCourses(): Promise { + const selected = this.selectedFilter, + selectedData = this.prefetchCoursesData[selected], + initialIcon = selectedData.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) => { + if (!this.isDestroyed) { + this.domUtils.showErrorModalDefault(error, 'core.course.errordownloadingcourse', true); + selectedData.icon = initialIcon; + } + }).finally(() => { + selectedData.badge = ''; + }); + } + + /** + * The selected courses have changed. + */ + selectedChanged(): void { + this.filteredCourses = this.courses[this.selectedFilter]; + } + + /** + * Show or hide the filter. + */ + switchFilter(): void { + this.showFilter = !this.showFilter; + this.courses.filter = ''; + this.filteredCourses = this.courses[this.selectedFilter]; + if (this.showFilter) { + setTimeout(() => { + this.searchbar.setFocus(); + }, 500); + } + } + + /** + * If switch button that enables the filter input is shown or not. + * + * @return {boolean} If switch button that enables the filter input is shown or not. + */ + showFilterSwitchButton(): boolean { + return this.loaded && this.courses[this.selectedFilter] && this.courses[this.selectedFilter].length > 5; + } + + /** + * Component being destroyed. + */ + ngOnDestroy(): void { + this.isDestroyed = true; + this.updateSiteObserver && this.updateSiteObserver.off(); + } +} diff --git a/src/addon/block/myoverview/lang/en.json b/src/addon/block/myoverview/lang/en.json new file mode 100644 index 000000000..ad5a1b7f7 --- /dev/null +++ b/src/addon/block/myoverview/lang/en.json @@ -0,0 +1,9 @@ +{ + "future": "Future", + "inprogress": "In progress", + "past": "Past", + "morecourses": "More courses", + "nocoursesfuture": "No future courses", + "nocoursesinprogress": "No in progress courses", + "nocoursespast": "No past courses" +} \ No newline at end of file diff --git a/src/addon/block/myoverview/myoverview.module.ts b/src/addon/block/myoverview/myoverview.module.ts new file mode 100644 index 000000000..d628b58d1 --- /dev/null +++ b/src/addon/block/myoverview/myoverview.module.ts @@ -0,0 +1,36 @@ +// (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 { CoreCoursesComponentsModule } from '@core/courses/components/components.module'; +import { AddonBlockMyOverviewComponent } from './component/myoverview'; + +@NgModule({ + declarations: [ + AddonBlockMyOverviewComponent + ], + imports: [ + IonicModule, + CoreComponentsModule, + CoreCoursesComponentsModule, + TranslateModule.forChild() + ], + exports: [ + AddonBlockMyOverviewComponent + ] +}) +export class AddonBlockMyOverviewModule {} diff --git a/src/core/courses/components/overview-events/core-courses-overview-events.html b/src/addon/block/timeline/components/events/addon-block-timeline-events.html similarity index 78% rename from src/core/courses/components/overview-events/core-courses-overview-events.html rename to src/addon/block/timeline/components/events/addon-block-timeline-events.html index 3e010a2a5..40f8e945c 100644 --- a/src/core/courses/components/overview-events/core-courses-overview-events.html +++ b/src/addon/block/timeline/components/events/addon-block-timeline-events.html @@ -12,28 +12,28 @@ - {{ 'core.courses.recentlyoverdue' | translate }} + {{ 'addon.block_timeline.recentlyoverdue' | translate }} - {{ 'core.courses.next7days' | translate }} + {{ 'addon.block_timeline.next7days' | translate }} - {{ 'core.courses.next30days' | translate }} + {{ 'addon.block_timeline.next30days' | translate }} - {{ 'core.courses.future' | translate }} + {{ 'addon.block_myoverview.future' | translate }} @@ -45,5 +45,5 @@ - - + + diff --git a/src/core/courses/components/overview-events/overview-events.scss b/src/addon/block/timeline/components/events/events.scss similarity index 100% rename from src/core/courses/components/overview-events/overview-events.scss rename to src/addon/block/timeline/components/events/events.scss diff --git a/src/core/courses/components/overview-events/overview-events.ts b/src/addon/block/timeline/components/events/events.ts similarity index 96% rename from src/core/courses/components/overview-events/overview-events.ts rename to src/addon/block/timeline/components/events/events.ts index c7d7d6e14..fdfdd9da1 100644 --- a/src/core/courses/components/overview-events/overview-events.ts +++ b/src/addon/block/timeline/components/events/events.ts @@ -26,10 +26,10 @@ import * as moment from 'moment'; * Directive to render a list of events in course overview. */ @Component({ - selector: 'core-courses-overview-events', - templateUrl: 'core-courses-overview-events.html' + selector: 'addon-block-timeline-events', + templateUrl: 'addon-block-timeline-events.html' }) -export class CoreCoursesOverviewEventsComponent implements OnChanges { +export class AddonBlockTimelineEventsComponent implements OnChanges { @Input() events: any[]; // The events to render. @Input() showCourse?: boolean | string; // Whether to show the course name. @Input() canLoadMore?: boolean; // Whether more events can be loaded. diff --git a/src/addon/block/timeline/components/timeline/addon-block-timeline.html b/src/addon/block/timeline/components/timeline/addon-block-timeline.html new file mode 100644 index 000000000..fa5b9fc0c --- /dev/null +++ b/src/addon/block/timeline/components/timeline/addon-block-timeline.html @@ -0,0 +1,21 @@ +
+ + {{ 'addon.block_timeline.sortbydates' | translate }} + {{ 'addon.block_timeline.sortbycourses' | translate }} + +
+ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/addon/block/timeline/components/timeline/timeline.ts b/src/addon/block/timeline/components/timeline/timeline.ts new file mode 100644 index 000000000..1d7b839b9 --- /dev/null +++ b/src/addon/block/timeline/components/timeline/timeline.ts @@ -0,0 +1,172 @@ +// (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, Injector } from '@angular/core'; +import * as moment from 'moment'; +import { CoreUtilsProvider } from '@providers/utils/utils'; +import { CoreCoursesProvider } from '@core/courses/providers/courses'; +import { CoreCoursesHelperProvider } from '@core/courses/providers/helper'; +import { CoreCourseOptionsDelegate } from '@core/course/providers/options-delegate'; +import { AddonBlockComponent } from '../../../classes/block-component'; +import { AddonBlockTimelineProvider } from '../../providers/timeline'; + +/** + * Component to render a timeline block. + */ +@Component({ + selector: 'addon-block-timeline', + templateUrl: 'addon-block-timeline.html' +}) +export class AddonBlockTimelineComponent extends AddonBlockComponent implements OnInit { + sort = 'sortbydates'; + timeline = { + events: [], + loaded: false, + canLoadMore: undefined + }; + timelineCourses = { + courses: [], + loaded: false, + canLoadMore: false + }; + + protected courseIds = []; + protected fetchContentDefaultError = 'Error getting timeline data.'; + + constructor(injector: Injector, private coursesProvider: CoreCoursesProvider, private utils: CoreUtilsProvider, + private timelineProvider: AddonBlockTimelineProvider, private courseOptionsDelegate: CoreCourseOptionsDelegate, + private coursesHelper: CoreCoursesHelperProvider) { + + super(injector, 'AddonBlockTimelineComponent'); + } + + /** + * Component being initialized. + */ + ngOnInit(): void { + super.ngOnInit(); + } + + /** + * Perform the invalidate content function. + * + * @return {Promise} Resolved when done. + */ + protected invalidateContent(): Promise { + const promises = []; + + promises.push(this.timelineProvider.invalidateActionEventsByTimesort()); + promises.push(this.timelineProvider.invalidateActionEventsByCourses()); + promises.push(this.coursesProvider.invalidateUserCourses()); + promises.push(this.courseOptionsDelegate.clearAndInvalidateCoursesOptions()); + if (this.courseIds.length > 0) { + promises.push(this.coursesProvider.invalidateCoursesByField('ids', this.courseIds.join(','))); + } + + return this.utils.allPromises(promises); + } + + /** + * Fetch the courses for my overview. + * + * @return {Promise} Promise resolved when done. + */ + protected fetchContent(): Promise { + if (this.sort == 'sortbydates') { + return this.fetchMyOverviewTimeline().finally(() => { + this.timeline.loaded = true; + }); + } else if (this.sort == 'sortbycourses') { + return this.fetchMyOverviewTimelineByCourses().finally(() => { + this.timelineCourses.loaded = true; + }); + } + } + + /** + * Load more events. + */ + loadMoreTimeline(): Promise { + return this.fetchMyOverviewTimeline(this.timeline.canLoadMore).catch((error) => { + this.domUtils.showErrorModalDefault(error, this.fetchContentDefaultError); + }); + } + + /** + * Load more events. + * + * @param {any} course Course. + * @return {Promise} Promise resolved when done. + */ + loadMoreCourse(course: any): Promise { + return this.timelineProvider.getActionEventsByCourse(course.id, course.canLoadMore).then((courseEvents) => { + course.events = course.events.concat(courseEvents.events); + course.canLoadMore = courseEvents.canLoadMore; + }).catch((error) => { + this.domUtils.showErrorModalDefault(error, this.fetchContentDefaultError); + }); + } + + /** + * Fetch the timeline. + * + * @param {number} [afterEventId] The last event id. + * @return {Promise} Promise resolved when done. + */ + protected fetchMyOverviewTimeline(afterEventId?: number): Promise { + return this.timelineProvider.getActionEventsByTimesort(afterEventId).then((events) => { + this.timeline.events = events.events; + this.timeline.canLoadMore = events.canLoadMore; + }); + } + + /** + * Fetch the timeline by courses. + * + * @return {Promise} Promise resolved when done. + */ + protected fetchMyOverviewTimelineByCourses(): Promise { + return this.coursesHelper.getUserCoursesWithOptions().then((courses) => { + const today = moment().unix(); + courses = courses.filter((course) => { + return course.startdate <= today && (!course.enddate || course.enddate >= today); + }); + + this.timelineCourses.courses = courses; + if (courses.length > 0) { + this.courseIds = courses.map((course) => { + return course.id; + }); + + return this.timelineProvider.getActionEventsByCourses(this.courseIds).then((courseEvents) => { + this.timelineCourses.courses.forEach((course) => { + course.events = courseEvents[course.id].events; + course.canLoadMore = courseEvents[course.id].canLoadMore; + }); + }); + } + }); + } + + /** + * Change timeline sort being viewed. + */ + switchSort(): void { + if (!this.timeline.loaded && this.sort == 'sortbydates') { + this.fetchContent(); + } else if (!this.timelineCourses.loaded && this.sort == 'sortbycourses') { + this.fetchContent(); + } + } +} diff --git a/src/addon/block/timeline/lang/en.json b/src/addon/block/timeline/lang/en.json new file mode 100644 index 000000000..86041e79f --- /dev/null +++ b/src/addon/block/timeline/lang/en.json @@ -0,0 +1,9 @@ +{ + "next30days": "Next 30 days", + "next7days": "Next 7 days", + "nocoursesinprogress": "No in progress courses", + "noevents": "No upcoming activities due", + "recentlyoverdue": "Recently overdue", + "sortbycourses": "Sort by courses", + "sortbydates": "Sort by dates" +} \ No newline at end of file diff --git a/src/core/courses/providers/my-overview.ts b/src/addon/block/timeline/providers/timeline.ts similarity index 88% rename from src/core/courses/providers/my-overview.ts rename to src/addon/block/timeline/providers/timeline.ts index d64d0dbca..5b629d012 100644 --- a/src/core/courses/providers/my-overview.ts +++ b/src/addon/block/timeline/providers/timeline.ts @@ -14,16 +14,16 @@ import { Injectable } from '@angular/core'; import { CoreSitesProvider } from '@providers/sites'; -import { CoreSite } from '@classes/site'; import * as moment from 'moment'; /** * Service that provides some features regarding course overview. */ @Injectable() -export class CoreCoursesMyOverviewProvider { +export class AddonBlockTimelineProvider { static EVENTS_LIMIT = 20; static EVENTS_LIMIT_PER_COURSE = 10; + // Cache key was maintained when moving the functions to this file. It comes from core myoverview. protected ROOT_CACHE_KEY = 'myoverview:'; constructor(private sitesProvider: CoreSitesProvider) { } @@ -44,7 +44,7 @@ export class CoreCoursesMyOverviewProvider { data: any = { timesortfrom: time, courseid: courseId, - limitnum: CoreCoursesMyOverviewProvider.EVENTS_LIMIT_PER_COURSE + limitnum: AddonBlockTimelineProvider.EVENTS_LIMIT_PER_COURSE }, preSets = { cacheKey: this.getActionEventsByCourseCacheKey(courseId) @@ -88,7 +88,7 @@ export class CoreCoursesMyOverviewProvider { data = { timesortfrom: time, courseids: courseIds, - limitnum: CoreCoursesMyOverviewProvider.EVENTS_LIMIT_PER_COURSE + limitnum: AddonBlockTimelineProvider.EVENTS_LIMIT_PER_COURSE }, preSets = { cacheKey: this.getActionEventsByCoursesCacheKey() @@ -131,7 +131,7 @@ export class CoreCoursesMyOverviewProvider { const time = moment().subtract(14, 'days').unix(), // Check two weeks ago. data: any = { timesortfrom: time, - limitnum: CoreCoursesMyOverviewProvider.EVENTS_LIMIT + limitnum: AddonBlockTimelineProvider.EVENTS_LIMIT }, preSets = { cacheKey: this.getActionEventsByTimesortCacheKey(afterEventId, data.limitnum), @@ -222,33 +222,6 @@ export class CoreCoursesMyOverviewProvider { }); } - /** - * Check if My Overview is disabled in a certain site. - * - * @param {CoreSite} [site] Site. If not defined, use current site. - * @return {boolean} Whether it's disabled. - */ - isDisabledInSite(site?: CoreSite): boolean { - site = site || this.sitesProvider.getCurrentSite(); - - return site.isFeatureDisabled('CoreMainMenuDelegate_CoreCourses'); - } - - /** - * Check if My Overview is available and not disabled. - * - * @return {Promise} Promise resolved with true if enabled, resolved with false otherwise. - */ - isEnabled(): Promise { - if (!this.isDisabledInSite()) { - return this.isAvailable().catch(() => { - return false; - }); - } - - return Promise.resolve(false); - } - /** * Handles course events, filtering and treating if more can be loaded. * @@ -258,7 +231,7 @@ export class CoreCoursesMyOverviewProvider { */ protected treatCourseEvents(course: any, timeFrom: number): { events: any[], canLoadMore: number } { const canLoadMore: number = - course.events.length >= CoreCoursesMyOverviewProvider.EVENTS_LIMIT_PER_COURSE ? course.lastid : undefined; + course.events.length >= AddonBlockTimelineProvider.EVENTS_LIMIT_PER_COURSE ? course.lastid : undefined; // Filter events by time in case it uses cache. course.events = course.events.filter((element) => { diff --git a/src/addon/block/timeline/timeline.module.ts b/src/addon/block/timeline/timeline.module.ts new file mode 100644 index 000000000..3c2f5fa80 --- /dev/null +++ b/src/addon/block/timeline/timeline.module.ts @@ -0,0 +1,47 @@ +// (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 { CoreDirectivesModule } from '@directives/directives.module'; +import { CorePipesModule } from '@pipes/pipes.module'; +import { CoreCoursesComponentsModule } from '@core/courses/components/components.module'; +import { AddonBlockTimelineComponent } from './components/timeline/timeline'; +import { AddonBlockTimelineEventsComponent } from './components/events/events'; +import { AddonBlockTimelineProvider } from './providers/timeline'; + +@NgModule({ + declarations: [ + AddonBlockTimelineComponent, + AddonBlockTimelineEventsComponent + ], + imports: [ + IonicModule, + CoreComponentsModule, + CoreDirectivesModule, + CorePipesModule, + CoreCoursesComponentsModule, + TranslateModule.forChild() + ], + exports: [ + AddonBlockTimelineComponent, + AddonBlockTimelineEventsComponent + ], + providers: [ + AddonBlockTimelineProvider + ] +}) +export class AddonBlockTimelineModule {} diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 62d151460..6fe4d4970 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -83,6 +83,8 @@ import { AddonCompetencyModule } from '@addon/competency/competency.module'; import { AddonCourseCompletionModule } from '@addon/coursecompletion/coursecompletion.module'; import { AddonUserProfileFieldModule } from '@addon/userprofilefield/userprofilefield.module'; import { AddonFilesModule } from '@addon/files/files.module'; +import { AddonBlockMyOverviewModule } from '@addon/block/myoverview/myoverview.module'; +import { AddonBlockTimelineModule } from '@addon/block/timeline/timeline.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'; @@ -192,6 +194,8 @@ export const CORE_PROVIDERS: any[] = [ AddonCourseCompletionModule, AddonUserProfileFieldModule, AddonFilesModule, + AddonBlockMyOverviewModule, + AddonBlockTimelineModule, AddonModAssignModule, AddonModBookModule, AddonModChatModule, diff --git a/src/assets/lang/en.json b/src/assets/lang/en.json index bd2f8e5a0..9e2f21c71 100644 --- a/src/assets/lang/en.json +++ b/src/assets/lang/en.json @@ -10,6 +10,20 @@ "addon.badges.issuername": "Issuer name", "addon.badges.nobadges": "There are no badges available.", "addon.badges.recipientdetails": "Recipient details", + "addon.block_myoverview.future": "Future", + "addon.block_myoverview.inprogress": "In progress", + "addon.block_myoverview.morecourses": "More courses", + "addon.block_myoverview.nocoursesfuture": "No future courses", + "addon.block_myoverview.nocoursesinprogress": "No in progress courses", + "addon.block_myoverview.nocoursespast": "No past courses", + "addon.block_myoverview.past": "Past", + "addon.block_timeline.next30days": "Next 30 days", + "addon.block_timeline.next7days": "Next 7 days", + "addon.block_timeline.nocoursesinprogress": "No in progress courses", + "addon.block_timeline.noevents": "No upcoming activities due", + "addon.block_timeline.recentlyoverdue": "Recently overdue", + "addon.block_timeline.sortbycourses": "Sort by courses", + "addon.block_timeline.sortbydates": "Sort by dates", "addon.calendar.calendar": "Calendar", "addon.calendar.calendarevents": "Calendar events", "addon.calendar.defaultnotificationtime": "Default notification time", @@ -1135,34 +1149,20 @@ "core.courses.errorselfenrol": "An error occurred while self enrolling.", "core.courses.filtermycourses": "Filter my courses", "core.courses.frontpage": "Front page", - "core.courses.future": "Future", - "core.courses.inprogress": "In progress", - "core.courses.morecourses": "More courses", "core.courses.mycourses": "My courses", - "core.courses.next30days": "Next 30 days", - "core.courses.next7days": "Next 7 days", "core.courses.nocourses": "No course information to show.", - "core.courses.nocoursesfuture": "No future courses", - "core.courses.nocoursesinprogress": "No in progress courses", - "core.courses.nocoursesoverview": "No courses", - "core.courses.nocoursespast": "No past courses", "core.courses.nocoursesyet": "No courses in this category", - "core.courses.noevents": "No upcoming activities due", "core.courses.nosearchresults": "No results", "core.courses.notenroled": "You are not enrolled in this course", "core.courses.notenrollable": "You cannot enrol yourself in this course.", "core.courses.password": "Enrolment key", - "core.courses.past": "Past", "core.courses.paymentrequired": "This course requires a payment for entry.", "core.courses.paypalaccepted": "PayPal payments accepted", - "core.courses.recentlyoverdue": "Recently overdue", "core.courses.search": "Search", "core.courses.searchcourses": "Search courses", "core.courses.searchcoursesadvice": "You can use the search courses button to find courses to access as a guest or enrol yourself in courses that allow it.", "core.courses.selfenrolment": "Self enrolment", "core.courses.sendpaymentbutton": "Send payment via PayPal", - "core.courses.sortbycourses": "Sort by courses", - "core.courses.sortbydates": "Sort by dates", "core.courses.timeline": "Timeline", "core.courses.totalcoursesearchresults": "Total courses: {{$a}}", "core.currentdevice": "Current device", diff --git a/src/core/courses/components/components.module.ts b/src/core/courses/components/components.module.ts index d873b3d82..14ce35cbc 100644 --- a/src/core/courses/components/components.module.ts +++ b/src/core/courses/components/components.module.ts @@ -21,13 +21,11 @@ import { CoreDirectivesModule } from '@directives/directives.module'; import { CorePipesModule } from '@pipes/pipes.module'; import { CoreCoursesCourseProgressComponent } from '../components/course-progress/course-progress'; import { CoreCoursesCourseListItemComponent } from '../components/course-list-item/course-list-item'; -import { CoreCoursesOverviewEventsComponent } from '../components/overview-events/overview-events'; @NgModule({ declarations: [ CoreCoursesCourseProgressComponent, - CoreCoursesCourseListItemComponent, - CoreCoursesOverviewEventsComponent + CoreCoursesCourseListItemComponent ], imports: [ CommonModule, @@ -41,8 +39,7 @@ import { CoreCoursesOverviewEventsComponent } from '../components/overview-event ], exports: [ CoreCoursesCourseProgressComponent, - CoreCoursesCourseListItemComponent, - CoreCoursesOverviewEventsComponent + CoreCoursesCourseListItemComponent ] }) export class CoreCoursesComponentsModule {} diff --git a/src/core/courses/courses.module.ts b/src/core/courses/courses.module.ts index 06243e676..8920a72fe 100644 --- a/src/core/courses/courses.module.ts +++ b/src/core/courses/courses.module.ts @@ -16,17 +16,17 @@ import { NgModule } from '@angular/core'; import { CoreCoursesProvider } from './providers/courses'; import { CoreCoursesHelperProvider } from './providers/helper'; import { CoreCoursesMainMenuHandler } from './providers/mainmenu-handler'; -import { CoreCoursesMyOverviewProvider } from './providers/my-overview'; +import { CoreCoursesDashboardProvider } from './providers/dashboard'; import { CoreCoursesCourseLinkHandler } from './providers/course-link-handler'; import { CoreCoursesIndexLinkHandler } from './providers/courses-index-link-handler'; -import { CoreCoursesMyOverviewLinkHandler } from './providers/my-overview-link-handler'; +import { CoreCoursesDashboardLinkHandler } from './providers/dashboard-link-handler'; import { CoreMainMenuDelegate } from '@core/mainmenu/providers/delegate'; import { CoreContentLinksDelegate } from '@core/contentlinks/providers/delegate'; // List of providers (without handlers). export const CORE_COURSES_PROVIDERS: any[] = [ CoreCoursesProvider, - CoreCoursesMyOverviewProvider, + CoreCoursesDashboardProvider, CoreCoursesHelperProvider ]; @@ -36,23 +36,23 @@ export const CORE_COURSES_PROVIDERS: any[] = [ ], providers: [ CoreCoursesProvider, - CoreCoursesMyOverviewProvider, + CoreCoursesDashboardProvider, CoreCoursesHelperProvider, CoreCoursesMainMenuHandler, CoreCoursesCourseLinkHandler, CoreCoursesIndexLinkHandler, - CoreCoursesMyOverviewLinkHandler + CoreCoursesDashboardLinkHandler ], exports: [] }) export class CoreCoursesModule { constructor(mainMenuDelegate: CoreMainMenuDelegate, contentLinksDelegate: CoreContentLinksDelegate, mainMenuHandler: CoreCoursesMainMenuHandler, courseLinkHandler: CoreCoursesCourseLinkHandler, - indexLinkHandler: CoreCoursesIndexLinkHandler, myOverviewLinkHandler: CoreCoursesMyOverviewLinkHandler) { + indexLinkHandler: CoreCoursesIndexLinkHandler, dashboardLinkHandler: CoreCoursesDashboardLinkHandler) { mainMenuDelegate.registerHandler(mainMenuHandler); contentLinksDelegate.registerHandler(courseLinkHandler); contentLinksDelegate.registerHandler(indexLinkHandler); - contentLinksDelegate.registerHandler(myOverviewLinkHandler); + contentLinksDelegate.registerHandler(dashboardLinkHandler); } } diff --git a/src/core/courses/lang/en.json b/src/core/courses/lang/en.json index 3d5c5af67..4bffa7112 100644 --- a/src/core/courses/lang/en.json +++ b/src/core/courses/lang/en.json @@ -14,34 +14,20 @@ "errorselfenrol": "An error occurred while self enrolling.", "filtermycourses": "Filter my courses", "frontpage": "Front page", - "future": "Future", - "inprogress": "In progress", - "morecourses": "More courses", "mycourses": "My courses", - "next30days": "Next 30 days", - "next7days": "Next 7 days", "nocourses": "No course information to show.", - "nocoursesfuture": "No future courses", - "nocoursesinprogress": "No in progress courses", - "nocoursesoverview": "No courses", - "nocoursespast": "No past courses", "nocoursesyet": "No courses in this category", - "noevents": "No upcoming activities due", "nosearchresults": "No results", "notenroled": "You are not enrolled in this course", "notenrollable": "You cannot enrol yourself in this course.", "password": "Enrolment key", - "past": "Past", "paymentrequired": "This course requires a payment for entry.", "paypalaccepted": "PayPal payments accepted", - "recentlyoverdue": "Recently overdue", "search": "Search", "searchcourses": "Search courses", "searchcoursesadvice": "You can use the search courses button to find courses to access as a guest or enrol yourself in courses that allow it.", "selfenrolment": "Self enrolment", "sendpaymentbutton": "Send payment via PayPal", - "sortbycourses": "Sort by courses", - "sortbydates": "Sort by dates", "timeline": "Timeline", "totalcoursesearchresults": "Total courses: {{$a}}" } \ No newline at end of file diff --git a/src/core/courses/pages/dashboard/dashboard.html b/src/core/courses/pages/dashboard/dashboard.html new file mode 100644 index 000000000..482a53c9b --- /dev/null +++ b/src/core/courses/pages/dashboard/dashboard.html @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/core/courses/pages/my-overview/my-overview.module.ts b/src/core/courses/pages/dashboard/dashboard.module.ts similarity index 74% rename from src/core/courses/pages/my-overview/my-overview.module.ts rename to src/core/courses/pages/dashboard/dashboard.module.ts index 7ebca45c6..6ce594cd4 100644 --- a/src/core/courses/pages/my-overview/my-overview.module.ts +++ b/src/core/courses/pages/dashboard/dashboard.module.ts @@ -15,23 +15,27 @@ import { NgModule } from '@angular/core'; import { IonicPageModule } from 'ionic-angular'; import { TranslateModule } from '@ngx-translate/core'; -import { CoreCoursesMyOverviewPage } from './my-overview'; +import { CoreCoursesDashboardPage } from './dashboard'; import { CoreComponentsModule } from '@components/components.module'; import { CoreDirectivesModule } from '@directives/directives.module'; import { CoreCoursesComponentsModule } from '../../components/components.module'; +import { AddonBlockMyOverviewModule } from '@addon/block/myoverview/myoverview.module'; +import { AddonBlockTimelineModule } from '@addon/block/timeline/timeline.module'; import { CoreSiteHomeComponentsModule } from '@core/sitehome/components/components.module'; @NgModule({ declarations: [ - CoreCoursesMyOverviewPage, + CoreCoursesDashboardPage, ], imports: [ CoreComponentsModule, CoreDirectivesModule, CoreCoursesComponentsModule, CoreSiteHomeComponentsModule, - IonicPageModule.forChild(CoreCoursesMyOverviewPage), + AddonBlockMyOverviewModule, + AddonBlockTimelineModule, + IonicPageModule.forChild(CoreCoursesDashboardPage), TranslateModule.forChild() ], }) -export class CoreCoursesMyOverviewPageModule {} +export class CoreCoursesDashboardPageModule {} diff --git a/src/core/courses/pages/my-overview/my-overview.scss b/src/core/courses/pages/dashboard/dashboard.scss similarity index 77% rename from src/core/courses/pages/my-overview/my-overview.scss rename to src/core/courses/pages/dashboard/dashboard.scss index 838e05192..41e889542 100644 --- a/src/core/courses/pages/my-overview/my-overview.scss +++ b/src/core/courses/pages/dashboard/dashboard.scss @@ -1,4 +1,4 @@ -ion-app.app-root page-core-courses-my-overview { +ion-app.app-root page-core-courses-dashboard { ion-badge.core-course-download-courses-progress { display: block; @include float(start); diff --git a/src/core/courses/pages/dashboard/dashboard.ts b/src/core/courses/pages/dashboard/dashboard.ts new file mode 100644 index 000000000..36a2ca410 --- /dev/null +++ b/src/core/courses/pages/dashboard/dashboard.ts @@ -0,0 +1,125 @@ +// (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, OnDestroy, ViewChild } from '@angular/core'; +import { IonicPage, NavController } from 'ionic-angular'; +import { CoreEventsProvider } from '@providers/events'; +import { CoreSitesProvider } from '@providers/sites'; +import { CoreCoursesProvider } from '../../providers/courses'; +import { CoreSiteHomeProvider } from '@core/sitehome/providers/sitehome'; +import { AddonBlockMyOverviewComponent } from '@addon/block/myoverview/component/myoverview'; +import { AddonBlockTimelineComponent } from '@addon/block/timeline/components/timeline/timeline'; +import { CoreTabsComponent } from '@components/tabs/tabs'; +import { CoreSiteHomeIndexComponent } from '@core/sitehome/components/index/index'; + +/** + * Page that displays the dashboard. + */ +@IonicPage({ segment: 'core-courses-dashboard' }) +@Component({ + selector: 'page-core-courses-dashboard', + templateUrl: 'dashboard.html', +}) +export class CoreCoursesDashboardPage implements OnDestroy { + @ViewChild(CoreTabsComponent) tabsComponent: CoreTabsComponent; + @ViewChild(CoreSiteHomeIndexComponent) siteHomeComponent: CoreSiteHomeIndexComponent; + @ViewChild(AddonBlockMyOverviewComponent) blockMyOverview: AddonBlockMyOverviewComponent; + @ViewChild(AddonBlockTimelineComponent) blockTimeline: AddonBlockTimelineComponent; + + firstSelectedTab: number; + siteHomeEnabled: boolean; + tabsReady = false; + tabShown = 'courses'; + searchEnabled: boolean; + tabs = []; + siteName: string; + + protected isDestroyed; + protected updateSiteObserver; + protected courseIds = ''; + + constructor(private navCtrl: NavController, private coursesProvider: CoreCoursesProvider, + private sitesProvider: CoreSitesProvider, private siteHomeProvider: CoreSiteHomeProvider, + private eventsProvider: CoreEventsProvider) { + this.loadSiteName(); + } + + /** + * View loaded. + */ + ionViewDidLoad(): void { + this.searchEnabled = !this.coursesProvider.isSearchCoursesDisabledInSite(); + + // Refresh the enabled flags if site is updated. + this.updateSiteObserver = this.eventsProvider.on(CoreEventsProvider.SITE_UPDATED, () => { + this.searchEnabled = !this.coursesProvider.isSearchCoursesDisabledInSite(); + this.loadSiteName(); + }); + + // Decide which tab to load first. + this.siteHomeProvider.isAvailable().then((enabled) => { + const site = this.sitesProvider.getCurrentSite(), + displaySiteHome = site.getInfo() && site.getInfo().userhomepage === 0; + + this.siteHomeEnabled = enabled; + this.firstSelectedTab = displaySiteHome ? 0 : 1; + this.tabsReady = true; + }); + } + + /** + * User entered the page. + */ + ionViewDidEnter(): void { + this.tabsComponent && this.tabsComponent.ionViewDidEnter(); + } + + /** + * User left the page. + */ + ionViewDidLeave(): void { + this.tabsComponent && this.tabsComponent.ionViewDidLeave(); + } + + /** + * The tab has changed. + * + * @param {string} tab Name of the new tab. + */ + tabChanged(tab: string): void { + this.tabShown = tab; + } + + /** + * Go to search courses. + */ + openSearch(): void { + this.navCtrl.push('CoreCoursesSearchPage'); + } + + /** + * Load the site name. + */ + protected loadSiteName(): void { + this.siteName = this.sitesProvider.getCurrentSite().getInfo().sitename; + } + + /** + * Component being destroyed. + */ + ngOnDestroy(): void { + this.isDestroyed = true; + this.updateSiteObserver && this.updateSiteObserver.off(); + } +} diff --git a/src/core/courses/pages/my-overview/my-overview.html b/src/core/courses/pages/my-overview/my-overview.html deleted file mode 100644 index 4055e823b..000000000 --- a/src/core/courses/pages/my-overview/my-overview.html +++ /dev/null @@ -1,109 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - {{ 'core.courses.inprogress' | translate }} - {{ 'core.courses.future' | translate }} - {{ 'core.courses.past' | translate }} - - -
- - {{prefetchCoursesData[courses.selected].badge}} - -
-
- - - - - - - - -
- - - - - - - -
-
-
-
-
- - - - - - - - - -
- - {{ 'core.courses.sortbydates' | translate }} - {{ 'core.courses.sortbycourses' | translate }} - -
- - - - - - - - - - - - - - - -
-
-
-
-
\ No newline at end of file diff --git a/src/core/courses/pages/my-overview/my-overview.ts b/src/core/courses/pages/my-overview/my-overview.ts deleted file mode 100644 index cb3b58745..000000000 --- a/src/core/courses/pages/my-overview/my-overview.ts +++ /dev/null @@ -1,511 +0,0 @@ -// (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, OnDestroy, ViewChild } from '@angular/core'; -import { IonicPage, Searchbar, NavController } from 'ionic-angular'; -import { CoreEventsProvider } from '@providers/events'; -import { CoreSitesProvider } from '@providers/sites'; -import { CoreDomUtilsProvider } from '@providers/utils/dom'; -import { CoreUtilsProvider } from '@providers/utils/utils'; -import { CoreCoursesProvider } from '../../providers/courses'; -import { CoreCoursesHelperProvider } from '../../providers/helper'; -import { CoreCoursesMyOverviewProvider } from '../../providers/my-overview'; -import { CoreCourseHelperProvider } from '@core/course/providers/helper'; -import { CoreCourseOptionsDelegate } from '@core/course/providers/options-delegate'; -import { AddonCourseCompletionProvider } from '@addon/coursecompletion/providers/coursecompletion'; -import { CoreSiteHomeProvider } from '@core/sitehome/providers/sitehome'; -import * as moment from 'moment'; -import { CoreTabsComponent } from '@components/tabs/tabs'; -import { CoreSiteHomeIndexComponent } from '@core/sitehome/components/index/index'; - -/** - * Page that displays My Overview. - */ -@IonicPage({ segment: 'core-courses-my-overview' }) -@Component({ - selector: 'page-core-courses-my-overview', - templateUrl: 'my-overview.html', -}) -export class CoreCoursesMyOverviewPage implements OnDestroy { - @ViewChild(CoreTabsComponent) tabsComponent: CoreTabsComponent; - @ViewChild('searchbar') searchbar: Searchbar; - @ViewChild(CoreSiteHomeIndexComponent) siteHomeComponent: CoreSiteHomeIndexComponent; - - firstSelectedTab: number; - siteHomeEnabled: boolean; - tabsReady = false; - tabShown = 'courses'; - timeline = { - sort: 'sortbydates', - events: [], - loaded: false, - canLoadMore: undefined - }; - timelineCourses = { - courses: [], - loaded: false, - canLoadMore: false - }; - courses = { - selected: 'inprogress', - loaded: false, - filter: '', - past: [], - inprogress: [], - future: [] - }; - showFilter = false; - searchEnabled: boolean; - filteredCourses: any[]; - tabs = []; - prefetchCoursesData = { - inprogress: {}, - past: {}, - future: {} - }; - downloadAllCoursesEnabled: boolean; - siteName: string; - - protected prefetchIconsInitialized = false; - protected isDestroyed; - protected updateSiteObserver; - protected courseIds = ''; - - constructor(private navCtrl: NavController, private coursesProvider: CoreCoursesProvider, - private domUtils: CoreDomUtilsProvider, private myOverviewProvider: CoreCoursesMyOverviewProvider, - private courseHelper: CoreCourseHelperProvider, private sitesProvider: CoreSitesProvider, - private siteHomeProvider: CoreSiteHomeProvider, private courseOptionsDelegate: CoreCourseOptionsDelegate, - private eventsProvider: CoreEventsProvider, private coursesHelper: CoreCoursesHelperProvider, - private utils: CoreUtilsProvider, private courseCompletionProvider: AddonCourseCompletionProvider) { - this.loadSiteName(); - } - - /** - * View loaded. - */ - ionViewDidLoad(): void { - this.searchEnabled = !this.coursesProvider.isSearchCoursesDisabledInSite(); - 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.searchEnabled = !this.coursesProvider.isSearchCoursesDisabledInSite(); - this.downloadAllCoursesEnabled = !this.coursesProvider.isDownloadCoursesDisabledInSite(); - - if (!wasEnabled && this.downloadAllCoursesEnabled && this.courses.loaded) { - // Download all courses is enabled now, initialize it. - this.initPrefetchCoursesIcons(); - } - - this.loadSiteName(); - }); - - // Decide which tab to load first. - this.siteHomeProvider.isAvailable().then((enabled) => { - const site = this.sitesProvider.getCurrentSite(), - displaySiteHome = site.getInfo() && site.getInfo().userhomepage === 0; - - this.siteHomeEnabled = enabled; - this.firstSelectedTab = displaySiteHome ? 0 : 1; - this.tabsReady = true; - }); - } - - /** - * User entered the page. - */ - ionViewDidEnter(): void { - this.tabsComponent && this.tabsComponent.ionViewDidEnter(); - } - - /** - * User left the page. - */ - ionViewDidLeave(): void { - this.tabsComponent && this.tabsComponent.ionViewDidLeave(); - } - - /** - * Fetch the timeline. - * - * @param {number} [afterEventId] The last event id. - * @return {Promise} Promise resolved when done. - */ - protected fetchMyOverviewTimeline(afterEventId?: number): Promise { - return this.myOverviewProvider.getActionEventsByTimesort(afterEventId).then((events) => { - this.timeline.events = events.events; - this.timeline.canLoadMore = events.canLoadMore; - }).catch((error) => { - this.domUtils.showErrorModalDefault(error, 'Error getting my overview data.'); - }); - } - - /** - * Fetch the timeline by courses. - * - * @return {Promise} Promise resolved when done. - */ - protected fetchMyOverviewTimelineByCourses(): Promise { - return this.fetchUserCourses().then((courses) => { - const today = moment().unix(); - let courseIds; - courses = courses.filter((course) => { - return course.startdate <= today && (!course.enddate || course.enddate >= today); - }); - - this.timelineCourses.courses = courses; - if (courses.length > 0) { - courseIds = courses.map((course) => { - return course.id; - }); - - return this.myOverviewProvider.getActionEventsByCourses(courseIds).then((courseEvents) => { - this.timelineCourses.courses.forEach((course) => { - course.events = courseEvents[course.id].events; - course.canLoadMore = courseEvents[course.id].canLoadMore; - }); - }); - } - }).catch((error) => { - this.domUtils.showErrorModalDefault(error, 'Error getting my overview data.'); - }); - } - - /** - * Fetch the courses for my overview. - * - * @return {Promise} Promise resolved when done. - */ - protected fetchMyOverviewCourses(): Promise { - return this.fetchUserCourses().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) => { - const today = moment().unix(); - - this.courses.past = []; - this.courses.inprogress = []; - this.courses.future = []; - - courses.forEach((course) => { - if ((course.enddate && course.enddate < today) || course.completed) { - // Courses that have already ended. - this.courses.past.push(course); - } else if (course.startdate > today) { - // Courses that have not started yet. - this.courses.future.push(course); - } else { - // Courses still in progress. - this.courses.inprogress.push(course); - } - }); - - this.courses.filter = ''; - this.showFilter = false; - this.filteredCourses = this.courses[this.courses.selected]; - - this.initPrefetchCoursesIcons(); - }).catch((error) => { - this.domUtils.showErrorModalDefault(error, 'Error getting my overview data.'); - }); - } - - /** - * Fetch user courses. - * - * @return {Promise} Promise resolved when done. - */ - protected fetchUserCourses(): Promise { - return this.coursesProvider.getUserCourses().then((courses) => { - const promises = [], - courseIds = courses.map((course) => { - return course.id; - }); - - if (this.coursesProvider.canGetAdminAndNavOptions()) { - // Load course options of the course. - promises.push(this.coursesProvider.getCoursesAdminAndNavOptions(courseIds).then((options) => { - courses.forEach((course) => { - course.navOptions = options.navOptions[course.id]; - course.admOptions = options.admOptions[course.id]; - }); - })); - } - - this.courseIds = courseIds.join(','); - - promises.push(this.coursesHelper.loadCoursesExtraInfo(courses)); - - return Promise.all(promises).then(() => { - return courses.sort((a, b) => { - const compareA = a.fullname.toLowerCase(), - compareB = b.fullname.toLowerCase(); - - return compareA.localeCompare(compareB); - }); - }); - }); - } - - /** - * Show or hide the filter. - */ - switchFilter(): void { - this.showFilter = !this.showFilter; - this.courses.filter = ''; - this.filteredCourses = this.courses[this.courses.selected]; - if (this.showFilter) { - setTimeout(() => { - this.searchbar.setFocus(); - }, 500); - } - } - - /** - * The filter has changed. - * - * @param {any} Received Event. - */ - filterChanged(event: any): void { - const newValue = event.target.value && event.target.value.trim().toLowerCase(); - if (!newValue || !this.courses[this.courses.selected]) { - this.filteredCourses = this.courses[this.courses.selected]; - } else { - this.filteredCourses = this.courses[this.courses.selected].filter((course) => { - return course.fullname.toLowerCase().indexOf(newValue) > -1; - }); - } - } - - /** - * Refresh the data. - * - * @param {any} refresher Refresher. - * @return {Promise} Promise resolved when done. - */ - refreshMyOverview(refresher: any): Promise { - const promises = []; - - if (this.tabShown == 'timeline') { - promises.push(this.myOverviewProvider.invalidateActionEventsByTimesort()); - promises.push(this.myOverviewProvider.invalidateActionEventsByCourses()); - } - - promises.push(this.coursesProvider.invalidateUserCourses().finally(() => { - // Invalidate course completion data. - return this.coursesProvider.getUserCourses().then((courses) => { - return this.utils.allPromises(courses.map((course) => { - return this.courseCompletionProvider.invalidateCourseCompletion(course.id); - })); - }); - })); - - promises.push(this.courseOptionsDelegate.clearAndInvalidateCoursesOptions()); - if (this.courseIds) { - promises.push(this.coursesProvider.invalidateCoursesByField('ids', this.courseIds)); - } - - return this.utils.allPromises(promises).finally(() => { - switch (this.tabShown) { - case 'timeline': - switch (this.timeline.sort) { - case 'sortbydates': - return this.fetchMyOverviewTimeline(); - case 'sortbycourses': - return this.fetchMyOverviewTimelineByCourses(); - default: - } - break; - case 'courses': - this.prefetchIconsInitialized = false; - - return this.fetchMyOverviewCourses(); - default: - } - }).finally(() => { - refresher.complete(); - }); - } - - /** - * Change timeline sort being viewed. - */ - switchSort(): void { - switch (this.timeline.sort) { - case 'sortbydates': - if (!this.timeline.loaded) { - this.fetchMyOverviewTimeline().finally(() => { - this.timeline.loaded = true; - }); - } - break; - case 'sortbycourses': - if (!this.timelineCourses.loaded) { - this.fetchMyOverviewTimelineByCourses().finally(() => { - this.timelineCourses.loaded = true; - }); - } - break; - default: - } - } - - /** - * The tab has changed. - * - * @param {string} tab Name of the new tab. - */ - tabChanged(tab: string): void { - this.tabShown = tab; - switch (this.tabShown) { - case 'timeline': - if (!this.timeline.loaded) { - this.fetchMyOverviewTimeline().finally(() => { - this.timeline.loaded = true; - }); - } - break; - case 'courses': - if (!this.courses.loaded) { - this.fetchMyOverviewCourses().finally(() => { - this.courses.loaded = true; - }); - } - break; - default: - } - } - - /** - * Load more events. - */ - loadMoreTimeline(): Promise { - return this.fetchMyOverviewTimeline(this.timeline.canLoadMore); - } - - /** - * Load more events. - * - * @param {any} course Course. - * @return {Promise} Promise resolved when done. - */ - loadMoreCourse(course: any): Promise { - return this.myOverviewProvider.getActionEventsByCourse(course.id, course.canLoadMore).then((courseEvents) => { - course.events = course.events.concat(courseEvents.events); - course.canLoadMore = courseEvents.canLoadMore; - }); - } - - /** - * Go to search courses. - */ - openSearch(): void { - this.navCtrl.push('CoreCoursesSearchPage'); - } - - /** - * The selected courses have changed. - */ - selectedChanged(): void { - this.filteredCourses = this.courses[this.courses.selected]; - } - - /** - * Prefetch all the shown courses. - * - * @return {Promise} Promise resolved when done. - */ - prefetchCourses(): Promise { - const selected = this.courses.selected, - selectedData = this.prefetchCoursesData[selected], - initialIcon = selectedData.icon; - - selectedData.icon = 'spinner'; - selectedData.badge = ''; - - return this.courseHelper.confirmAndPrefetchCourses(this.courses[selected], (progress) => { - selectedData.badge = progress.count + ' / ' + progress.total; - }).then(() => { - selectedData.icon = 'refresh'; - }).catch((error) => { - if (!this.isDestroyed) { - this.domUtils.showErrorModalDefault(error, 'core.course.errordownloadingcourse', true); - selectedData.icon = initialIcon; - } - }).finally(() => { - selectedData.badge = ''; - }); - } - - /** - * Initialize the prefetch icon for selected courses. - */ - protected initPrefetchCoursesIcons(): void { - if (this.prefetchIconsInitialized || !this.downloadAllCoursesEnabled) { - // Already initialized. - return; - } - - 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; - }); - - }); - } - - /** - * Load the site name. - */ - protected loadSiteName(): void { - this.siteName = this.sitesProvider.getCurrentSite().getInfo().sitename; - } - - /** - * Component being destroyed. - */ - ngOnDestroy(): void { - this.isDestroyed = true; - this.updateSiteObserver && this.updateSiteObserver.off(); - } -} diff --git a/src/core/courses/providers/my-overview-link-handler.ts b/src/core/courses/providers/dashboard-link-handler.ts similarity index 91% rename from src/core/courses/providers/my-overview-link-handler.ts rename to src/core/courses/providers/dashboard-link-handler.ts index 6bd87143a..bd325c80e 100644 --- a/src/core/courses/providers/my-overview-link-handler.ts +++ b/src/core/courses/providers/dashboard-link-handler.ts @@ -21,7 +21,7 @@ import { CoreLoginHelperProvider } from '@core/login/providers/helper'; * Handler to treat links to my overview. */ @Injectable() -export class CoreCoursesMyOverviewLinkHandler extends CoreContentLinksHandlerBase { +export class CoreCoursesDashboardLinkHandler extends CoreContentLinksHandlerBase { name = 'CoreCoursesMyOverviewLinkHandler'; featureName = 'CoreMainMenuDelegate_CoreCourses'; pattern = /\/my\/?$/; @@ -44,7 +44,7 @@ export class CoreCoursesMyOverviewLinkHandler extends CoreContentLinksHandlerBas return [{ action: (siteId, navCtrl?): void => { // Always use redirect to make it the new history root (to avoid "loops" in history). - this.loginHelper.redirect('CoreCoursesMyOverviewPage', undefined, siteId); + this.loginHelper.redirect('CoreCoursesDashboardPage', undefined, siteId); } }]; } diff --git a/src/core/courses/providers/dashboard.ts b/src/core/courses/providers/dashboard.ts new file mode 100644 index 000000000..fbc56d3cc --- /dev/null +++ b/src/core/courses/providers/dashboard.ts @@ -0,0 +1,64 @@ +// (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 } from '@angular/core'; +import { CoreSitesProvider } from '@providers/sites'; +import { CoreSite } from '@classes/site'; +import { AddonBlockTimelineProvider } from '@addon/block/timeline/providers/timeline'; + +/** + * Service that provides some features regarding course overview. + */ +@Injectable() +export class CoreCoursesDashboardProvider { + + constructor(private sitesProvider: CoreSitesProvider, private timelineProvider: AddonBlockTimelineProvider) { } + + /** + * Returns whether or not My Overview is available for a certain site. + * + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved with true if available, resolved with false or rejected otherwise. + */ + isAvailable(siteId?: string): Promise { + return this.timelineProvider.isAvailable(siteId); + } + + /** + * Check if My Overview is disabled in a certain site. + * + * @param {CoreSite} [site] Site. If not defined, use current site. + * @return {boolean} Whether it's disabled. + */ + isDisabledInSite(site?: CoreSite): boolean { + site = site || this.sitesProvider.getCurrentSite(); + + return site.isFeatureDisabled('CoreMainMenuDelegate_CoreCourses'); + } + + /** + * Check if My Overview is available and not disabled. + * + * @return {Promise} Promise resolved with true if enabled, resolved with false otherwise. + */ + isEnabled(): Promise { + if (!this.isDisabledInSite()) { + return this.isAvailable().catch(() => { + return false; + }); + } + + return Promise.resolve(false); + } +} diff --git a/src/core/courses/providers/helper.ts b/src/core/courses/providers/helper.ts index 2ecf338c0..078cd3e37 100644 --- a/src/core/courses/providers/helper.ts +++ b/src/core/courses/providers/helper.ts @@ -72,4 +72,39 @@ export class CoreCoursesHelperProvider { }); }); } + + /** + * Get user courses with admin and nav options. + * + * @return {Promise} Promise resolved when done. + */ + getUserCoursesWithOptions(): Promise { + return this.coursesProvider.getUserCourses().then((courses) => { + const promises = [], + courseIds = courses.map((course) => { + return course.id; + }); + + if (this.coursesProvider.canGetAdminAndNavOptions()) { + // Load course options of the course. + promises.push(this.coursesProvider.getCoursesAdminAndNavOptions(courseIds).then((options) => { + courses.forEach((course) => { + course.navOptions = options.navOptions[course.id]; + course.admOptions = options.admOptions[course.id]; + }); + })); + } + + promises.push(this.loadCoursesExtraInfo(courses)); + + return Promise.all(promises).then(() => { + return courses.sort((a, b) => { + const compareA = a.fullname.toLowerCase(), + compareB = b.fullname.toLowerCase(); + + return compareA.localeCompare(compareB); + }); + }); + }); + } } diff --git a/src/core/courses/providers/mainmenu-handler.ts b/src/core/courses/providers/mainmenu-handler.ts index aad553987..172cb7378 100644 --- a/src/core/courses/providers/mainmenu-handler.ts +++ b/src/core/courses/providers/mainmenu-handler.ts @@ -15,7 +15,7 @@ import { Injectable } from '@angular/core'; import { CoreCoursesProvider } from './courses'; import { CoreMainMenuHandler, CoreMainMenuHandlerData } from '@core/mainmenu/providers/delegate'; -import { CoreCoursesMyOverviewProvider } from '../providers/my-overview'; +import { CoreCoursesDashboardProvider } from '../providers/dashboard'; /** * Handler to add My Courses or My Overview into main menu. @@ -24,9 +24,9 @@ import { CoreCoursesMyOverviewProvider } from '../providers/my-overview'; export class CoreCoursesMainMenuHandler implements CoreMainMenuHandler { name = 'CoreCourses'; priority = 1100; - isOverviewEnabled: boolean; + isDashboardEnabled: boolean; - constructor(private coursesProvider: CoreCoursesProvider, private myOverviewProvider: CoreCoursesMyOverviewProvider) { } + constructor(private coursesProvider: CoreCoursesProvider, private dashboardProvider: CoreCoursesDashboardProvider) { } /** * Check if the handler is enabled on a site level. @@ -35,8 +35,8 @@ export class CoreCoursesMainMenuHandler implements CoreMainMenuHandler { */ isEnabled(): boolean | Promise { // Check if my overview is enabled. - return this.myOverviewProvider.isEnabled().then((enabled) => { - this.isOverviewEnabled = enabled; + return this.dashboardProvider.isEnabled().then((enabled) => { + this.isDashboardEnabled = enabled; if (enabled) { return true; } @@ -52,11 +52,11 @@ export class CoreCoursesMainMenuHandler implements CoreMainMenuHandler { * @return {CoreMainMenuHandlerData} Data needed to render the handler. */ getDisplayData(): CoreMainMenuHandlerData { - if (this.isOverviewEnabled) { + if (this.isDashboardEnabled) { return { icon: 'home', title: 'core.courses.courseoverview', - page: 'CoreCoursesMyOverviewPage', + page: 'CoreCoursesDashboardPage', class: 'core-courseoverview-handler' }; } else { diff --git a/src/core/sitehome/providers/mainmenu-handler.ts b/src/core/sitehome/providers/mainmenu-handler.ts index 0ebb62738..6c81774ed 100644 --- a/src/core/sitehome/providers/mainmenu-handler.ts +++ b/src/core/sitehome/providers/mainmenu-handler.ts @@ -15,7 +15,7 @@ import { Injectable } from '@angular/core'; import { CoreSiteHomeProvider } from './sitehome'; import { CoreMainMenuHandler, CoreMainMenuHandlerData } from '@core/mainmenu/providers/delegate'; -import { CoreCoursesMyOverviewProvider } from '@core/courses/providers/my-overview'; +import { CoreCoursesDashboardProvider } from '@core/courses/providers/dashboard'; /** * Handler to add Site Home into main menu. @@ -24,9 +24,8 @@ import { CoreCoursesMyOverviewProvider } from '@core/courses/providers/my-overvi export class CoreSiteHomeMainMenuHandler implements CoreMainMenuHandler { name = 'CoreSiteHome'; priority = 1200; - isOverviewEnabled: boolean; - constructor(private siteHomeProvider: CoreSiteHomeProvider, private myOverviewProvider: CoreCoursesMyOverviewProvider) { } + constructor(private siteHomeProvider: CoreSiteHomeProvider, private dashboardProvider: CoreCoursesDashboardProvider) { } /** * Check if the handler is enabled on a site level. @@ -35,7 +34,7 @@ export class CoreSiteHomeMainMenuHandler implements CoreMainMenuHandler { */ isEnabled(): boolean | Promise { // Check if my overview is enabled. - return this.myOverviewProvider.isEnabled().then((enabled) => { + return this.dashboardProvider.isEnabled().then((enabled) => { if (enabled) { // My overview is enabled, Site Home will be inside the overview page. return false;