MOBILE-2667 blocks: Move my overview and timeline to blocks
This commit is contained in:
		
							parent
							
								
									cdb4e3078d
								
							
						
					
					
						commit
						7e2f2dfd4f
					
				| @ -10,6 +10,20 @@ | |||||||
|   "addon.badges.issuername": "badges", |   "addon.badges.issuername": "badges", | ||||||
|   "addon.badges.nobadges": "badges", |   "addon.badges.nobadges": "badges", | ||||||
|   "addon.badges.recipientdetails": "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.calendar": "calendar", | ||||||
|   "addon.calendar.calendarevents": "local_moodlemobileapp", |   "addon.calendar.calendarevents": "local_moodlemobileapp", | ||||||
|   "addon.calendar.defaultnotificationtime": "local_moodlemobileapp", |   "addon.calendar.defaultnotificationtime": "local_moodlemobileapp", | ||||||
| @ -1135,34 +1149,20 @@ | |||||||
|   "core.courses.errorselfenrol": "local_moodlemobileapp", |   "core.courses.errorselfenrol": "local_moodlemobileapp", | ||||||
|   "core.courses.filtermycourses": "local_moodlemobileapp", |   "core.courses.filtermycourses": "local_moodlemobileapp", | ||||||
|   "core.courses.frontpage": "admin", |   "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.mycourses": "moodle", | ||||||
|   "core.courses.next30days": "block_timeline", |  | ||||||
|   "core.courses.next7days": "block_timeline", |  | ||||||
|   "core.courses.nocourses": "my", |   "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.nocoursesyet": "moodle", | ||||||
|   "core.courses.noevents": "block_timeline", |  | ||||||
|   "core.courses.nosearchresults": "wiki", |   "core.courses.nosearchresults": "wiki", | ||||||
|   "core.courses.notenroled": "completion", |   "core.courses.notenroled": "completion", | ||||||
|   "core.courses.notenrollable": "local_moodlemobileapp", |   "core.courses.notenrollable": "local_moodlemobileapp", | ||||||
|   "core.courses.password": "local_moodlemobileapp", |   "core.courses.password": "local_moodlemobileapp", | ||||||
|   "core.courses.past": "block_myoverview", |  | ||||||
|   "core.courses.paymentrequired": "moodle", |   "core.courses.paymentrequired": "moodle", | ||||||
|   "core.courses.paypalaccepted": "enrol_paypal", |   "core.courses.paypalaccepted": "enrol_paypal", | ||||||
|   "core.courses.recentlyoverdue": "local_moodlemobileapp", |  | ||||||
|   "core.courses.search": "moodle", |   "core.courses.search": "moodle", | ||||||
|   "core.courses.searchcourses": "moodle", |   "core.courses.searchcourses": "moodle", | ||||||
|   "core.courses.searchcoursesadvice": "local_moodlemobileapp", |   "core.courses.searchcoursesadvice": "local_moodlemobileapp", | ||||||
|   "core.courses.selfenrolment": "local_moodlemobileapp", |   "core.courses.selfenrolment": "local_moodlemobileapp", | ||||||
|   "core.courses.sendpaymentbutton": "enrol_paypal", |   "core.courses.sendpaymentbutton": "enrol_paypal", | ||||||
|   "core.courses.sortbycourses": "block_timeline", |  | ||||||
|   "core.courses.sortbydates": "block_timeline", |  | ||||||
|   "core.courses.timeline": "block_dashboard", |   "core.courses.timeline": "block_dashboard", | ||||||
|   "core.courses.totalcoursesearchresults": "local_moodlemobileapp", |   "core.courses.totalcoursesearchresults": "local_moodlemobileapp", | ||||||
|   "core.currentdevice": "local_moodlemobileapp", |   "core.currentdevice": "local_moodlemobileapp", | ||||||
|  | |||||||
							
								
								
									
										138
									
								
								src/addon/block/classes/block-component.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										138
									
								
								src/addon/block/classes/block-component.ts
									
									
									
									
									
										Normal file
									
								
							| @ -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<any>} Promise resolved when done. | ||||||
|  |      */ | ||||||
|  |     doRefresh(refresher?: any, done?: () => void, showErrors: boolean = false): Promise<any> { | ||||||
|  |         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<any>} Resolved when done. | ||||||
|  |      */ | ||||||
|  |     protected refreshContent(showErrors: boolean = false): Promise<any> { | ||||||
|  |         // 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<any>} Resolved when done. | ||||||
|  |      */ | ||||||
|  |     protected invalidateContent(): Promise<any> { | ||||||
|  |         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<any>} Promise resolved when done. | ||||||
|  |      */ | ||||||
|  |     protected loadContent(refresh?: boolean, showErrors: boolean = false): Promise<any> { | ||||||
|  |         // 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<any>} Promise resolved when done. | ||||||
|  |      */ | ||||||
|  |     protected fetchContent(refresh?: boolean): Promise<any> { | ||||||
|  |         return Promise.resolve(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
| @ -0,0 +1,42 @@ | |||||||
|  | <!-- Buttons to add to the header. --> | ||||||
|  | <core-navbar-buttons end> | ||||||
|  |      <button [hidden]="!showFilterSwitchButton()" ion-button icon-only [attr.aria-label]="'core.courses.filtermycourses' | translate" (click)="switchFilter()"> | ||||||
|  |         <ion-icon name="funnel"></ion-icon> | ||||||
|  |     </button> | ||||||
|  | </core-navbar-buttons> | ||||||
|  | 
 | ||||||
|  | <core-loading [hideUntil]="loaded" class="core-loading-center"> | ||||||
|  |     <!-- "Time" selector. --> | ||||||
|  |     <div padding class="clearfix" [hidden]="showFilter" ion-row justify-content-between> | ||||||
|  |         <ion-select float-start [title]="'core.show' | translate" [(ngModel)]="selectedFilter" ion-col (ngModelChange)="selectedChanged()" interface="popover" class="core-button-select"> | ||||||
|  |             <ion-option value="inprogress">{{ 'addon.block_myoverview.inprogress' | translate }}</ion-option> | ||||||
|  |             <ion-option value="future">{{ 'addon.block_myoverview.future' | translate }}</ion-option> | ||||||
|  |             <ion-option value="past">{{ 'addon.block_myoverview.past' | translate }}</ion-option> | ||||||
|  |         </ion-select> | ||||||
|  |         <!-- Download all courses. --> | ||||||
|  |         <div *ngIf="downloadAllCoursesEnabled && courses[selectedFilter] && courses[selectedFilter].length > 1" class="core-button-spinner"> | ||||||
|  |             <button *ngIf="prefetchCoursesData[selectedFilter].icon && prefetchCoursesData[selectedFilter].icon != 'spinner'" ion-button icon-only clear color="dark" (click)="prefetchCourses()"> | ||||||
|  |                 <core-icon [name]="prefetchCoursesData[selectedFilter].icon"></core-icon> | ||||||
|  |             </button> | ||||||
|  |             <ion-badge class="core-course-download-courses-progress" *ngIf="prefetchCoursesData[selectedFilter].badge">{{prefetchCoursesData[selectedFilter].badge}}</ion-badge> | ||||||
|  |             <ion-spinner *ngIf="!prefetchCoursesData[selectedFilter].icon || prefetchCoursesData[selectedFilter].icon == 'spinner'"></ion-spinner> | ||||||
|  |         </div> | ||||||
|  |     </div> | ||||||
|  |     <core-empty-box *ngIf="courses[selectedFilter].length == 0 && selectedFilter == 'inprogress'" image="assets/img/icons/courses.svg" [message]="'addon.block_myoverview.nocoursesinprogress' | translate"></core-empty-box> | ||||||
|  |     <core-empty-box *ngIf="courses[selectedFilter].length == 0 && selectedFilter == 'future'" image="assets/img/icons/courses.svg" [message]="'addon.block_myoverview.nocoursesfuture' | translate"></core-empty-box> | ||||||
|  |     <core-empty-box *ngIf="courses[selectedFilter].length == 0 && selectedFilter == 'past'" image="assets/img/icons/courses.svg" [message]="'addon.block_myoverview.nocoursespast' | translate"></core-empty-box> | ||||||
|  | 
 | ||||||
|  |     <!-- Filter courses. --> | ||||||
|  |     <ion-searchbar #searchbar *ngIf="showFilter" [(ngModel)]="courses.filter" (ionInput)="filterChanged($event)" (ionCancel)="filterChanged()" [placeholder]="'core.courses.filtermycourses' | translate"> | ||||||
|  |     </ion-searchbar> | ||||||
|  |     <!-- List of courses. --> | ||||||
|  |     <div> | ||||||
|  |         <ion-grid no-padding> | ||||||
|  |             <ion-row no-padding> | ||||||
|  |                 <ion-col *ngFor="let course of filteredCourses" no-padding col-12 col-sm-6 col-md-6 col-lg-4 col-xl-4 align-self-stretch> | ||||||
|  |                     <core-courses-course-progress [course]="course" class="core-courseoverview"></core-courses-course-progress> | ||||||
|  |                 </ion-col> | ||||||
|  |             </ion-row> | ||||||
|  |         </ion-grid> | ||||||
|  |     </div> | ||||||
|  | </core-loading> | ||||||
							
								
								
									
										277
									
								
								src/addon/block/myoverview/component/myoverview.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										277
									
								
								src/addon/block/myoverview/component/myoverview.ts
									
									
									
									
									
										Normal file
									
								
							| @ -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<any>} Resolved when done. | ||||||
|  |      */ | ||||||
|  |     protected invalidateContent(): Promise<any> { | ||||||
|  |         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<any>} Promise resolved when done. | ||||||
|  |      */ | ||||||
|  |     protected fetchContent(): Promise<any> { | ||||||
|  |         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<any>} Promise resolved when done. | ||||||
|  |      */ | ||||||
|  |     prefetchCourses(): Promise<any> { | ||||||
|  |         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(); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										9
									
								
								src/addon/block/myoverview/lang/en.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/addon/block/myoverview/lang/en.json
									
									
									
									
									
										Normal file
									
								
							| @ -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" | ||||||
|  | } | ||||||
							
								
								
									
										36
									
								
								src/addon/block/myoverview/myoverview.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src/addon/block/myoverview/myoverview.module.ts
									
									
									
									
									
										Normal file
									
								
							| @ -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 {} | ||||||
| @ -12,28 +12,28 @@ | |||||||
| </ng-template> | </ng-template> | ||||||
| 
 | 
 | ||||||
| <ion-item-group *ngIf="recentlyOverdue.length > 0"> | <ion-item-group *ngIf="recentlyOverdue.length > 0"> | ||||||
|     <ion-item-divider color="danger">{{ 'core.courses.recentlyoverdue' | translate }}</ion-item-divider> |     <ion-item-divider color="danger">{{ 'addon.block_timeline.recentlyoverdue' | translate }}</ion-item-divider> | ||||||
|     <ng-container *ngFor="let event of recentlyOverdue"> |     <ng-container *ngFor="let event of recentlyOverdue"> | ||||||
|         <ng-container *ngTemplateOutlet="eventTemplate; context: {event: event}"></ng-container> |         <ng-container *ngTemplateOutlet="eventTemplate; context: {event: event}"></ng-container> | ||||||
|     </ng-container> |     </ng-container> | ||||||
| </ion-item-group> | </ion-item-group> | ||||||
| 
 | 
 | ||||||
| <ion-item-group *ngIf="next7Days.length > 0"> | <ion-item-group *ngIf="next7Days.length > 0"> | ||||||
|     <ion-item-divider color="light">{{ 'core.courses.next7days' | translate }}</ion-item-divider> |     <ion-item-divider color="light">{{ 'addon.block_timeline.next7days' | translate }}</ion-item-divider> | ||||||
|     <ng-container *ngFor="let event of next7Days"> |     <ng-container *ngFor="let event of next7Days"> | ||||||
|         <ng-container *ngTemplateOutlet="eventTemplate; context: {event: event}"></ng-container> |         <ng-container *ngTemplateOutlet="eventTemplate; context: {event: event}"></ng-container> | ||||||
|     </ng-container> |     </ng-container> | ||||||
| </ion-item-group> | </ion-item-group> | ||||||
| 
 | 
 | ||||||
| <ion-item-group *ngIf="next30Days.length > 0"> | <ion-item-group *ngIf="next30Days.length > 0"> | ||||||
|     <ion-item-divider color="light">{{ 'core.courses.next30days' | translate }}</ion-item-divider> |     <ion-item-divider color="light">{{ 'addon.block_timeline.next30days' | translate }}</ion-item-divider> | ||||||
|     <ng-container *ngFor="let event of next30Days"> |     <ng-container *ngFor="let event of next30Days"> | ||||||
|         <ng-container *ngTemplateOutlet="eventTemplate; context: {event: event}"></ng-container> |         <ng-container *ngTemplateOutlet="eventTemplate; context: {event: event}"></ng-container> | ||||||
|     </ng-container> |     </ng-container> | ||||||
| </ion-item-group> | </ion-item-group> | ||||||
| 
 | 
 | ||||||
| <ion-item-group *ngIf="future.length > 0"> | <ion-item-group *ngIf="future.length > 0"> | ||||||
|     <ion-item-divider color="light">{{ 'core.courses.future' | translate }}</ion-item-divider> |     <ion-item-divider color="light">{{ 'addon.block_myoverview.future' | translate }}</ion-item-divider> | ||||||
|     <ng-container *ngFor="let event of future"> |     <ng-container *ngFor="let event of future"> | ||||||
|         <ng-container *ngTemplateOutlet="eventTemplate; context: {event: event}"></ng-container> |         <ng-container *ngTemplateOutlet="eventTemplate; context: {event: event}"></ng-container> | ||||||
|     </ng-container> |     </ng-container> | ||||||
| @ -45,5 +45,5 @@ | |||||||
|     <ion-spinner *ngIf="loadingMore"></ion-spinner> |     <ion-spinner *ngIf="loadingMore"></ion-spinner> | ||||||
| </div> | </div> | ||||||
| 
 | 
 | ||||||
| <core-empty-box *ngIf="empty && showCourse" image="assets/img/icons/activities.svg" [message]="'core.courses.noevents' | translate"></core-empty-box> | <core-empty-box *ngIf="empty && showCourse" image="assets/img/icons/activities.svg" [message]="'addon.block_timeline.noevents' | translate"></core-empty-box> | ||||||
| <core-empty-box *ngIf="empty && !showCourse" [message]="'core.courses.noevents' | translate"></core-empty-box> | <core-empty-box *ngIf="empty && !showCourse" [message]="'addon.block_timeline.noevents' | translate"></core-empty-box> | ||||||
| @ -26,10 +26,10 @@ import * as moment from 'moment'; | |||||||
|  * Directive to render a list of events in course overview. |  * Directive to render a list of events in course overview. | ||||||
|  */ |  */ | ||||||
| @Component({ | @Component({ | ||||||
|     selector: 'core-courses-overview-events', |     selector: 'addon-block-timeline-events', | ||||||
|     templateUrl: 'core-courses-overview-events.html' |     templateUrl: 'addon-block-timeline-events.html' | ||||||
| }) | }) | ||||||
| export class CoreCoursesOverviewEventsComponent implements OnChanges { | export class AddonBlockTimelineEventsComponent implements OnChanges { | ||||||
|     @Input() events: any[]; // The events to render.
 |     @Input() events: any[]; // The events to render.
 | ||||||
|     @Input() showCourse?: boolean | string; // Whether to show the course name.
 |     @Input() showCourse?: boolean | string; // Whether to show the course name.
 | ||||||
|     @Input() canLoadMore?: boolean; // Whether more events can be loaded.
 |     @Input() canLoadMore?: boolean; // Whether more events can be loaded.
 | ||||||
| @ -0,0 +1,21 @@ | |||||||
|  | <div padding [hidden]="!loaded"> | ||||||
|  |     <ion-select [(ngModel)]="sort" (ngModelChange)="switchSort()" interface="popover" class="core-button-select"> | ||||||
|  |         <ion-option value="sortbydates">{{ 'addon.block_timeline.sortbydates' | translate }}</ion-option> | ||||||
|  |         <ion-option value="sortbycourses">{{ 'addon.block_timeline.sortbycourses' | translate }}</ion-option> | ||||||
|  |     </ion-select> | ||||||
|  | </div> | ||||||
|  | <core-loading [hideUntil]="loaded && timeline.loaded" [hidden]="sort != 'sortbydates'" class="core-loading-center"> | ||||||
|  |     <addon-block-timeline-events [events]="timeline.events" showCourse="true" [canLoadMore]="timeline.canLoadMore" (loadMore)="loadMoreTimeline()"></addon-block-timeline-events> | ||||||
|  | </core-loading> | ||||||
|  | <core-loading [hideUntil]="loaded && timelineCourses.loaded" [hidden]="sort != 'sortbycourses'" class="core-loading-center"> | ||||||
|  |     <ion-grid no-padding> | ||||||
|  |         <ion-row no-padding> | ||||||
|  |             <ion-col *ngFor="let course of timelineCourses.courses" no-padding col-12 col-md-6> | ||||||
|  |                 <core-courses-course-progress [course]="course"> | ||||||
|  |                     <addon-block-timeline-events [events]="course.events" [canLoadMore]="course.canLoadMore" (loadMore)="loadMoreCourse(course)"></addon-block-timeline-events> | ||||||
|  |                 </core-courses-course-progress> | ||||||
|  |             </ion-col> | ||||||
|  |         </ion-row> | ||||||
|  |     </ion-grid> | ||||||
|  |     <core-empty-box *ngIf="timelineCourses.courses.length == 0" image="assets/img/icons/courses.svg" [message]="'addon.block_timeline.nocoursesinprogress' | translate"></core-empty-box> | ||||||
|  | </core-loading> | ||||||
							
								
								
									
										172
									
								
								src/addon/block/timeline/components/timeline/timeline.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										172
									
								
								src/addon/block/timeline/components/timeline/timeline.ts
									
									
									
									
									
										Normal file
									
								
							| @ -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<any>} Resolved when done. | ||||||
|  |      */ | ||||||
|  |     protected invalidateContent(): Promise<any> { | ||||||
|  |         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<any>} Promise resolved when done. | ||||||
|  |      */ | ||||||
|  |     protected fetchContent(): Promise<any> { | ||||||
|  |         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<any> { | ||||||
|  |         return this.fetchMyOverviewTimeline(this.timeline.canLoadMore).catch((error) => { | ||||||
|  |             this.domUtils.showErrorModalDefault(error, this.fetchContentDefaultError); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Load more events. | ||||||
|  |      * | ||||||
|  |      * @param {any} course Course. | ||||||
|  |      * @return {Promise<any>} Promise resolved when done. | ||||||
|  |      */ | ||||||
|  |     loadMoreCourse(course: any): Promise<any> { | ||||||
|  |         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<any>} Promise resolved when done. | ||||||
|  |      */ | ||||||
|  |     protected fetchMyOverviewTimeline(afterEventId?: number): Promise<any> { | ||||||
|  |         return this.timelineProvider.getActionEventsByTimesort(afterEventId).then((events) => { | ||||||
|  |             this.timeline.events = events.events; | ||||||
|  |             this.timeline.canLoadMore = events.canLoadMore; | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Fetch the timeline by courses. | ||||||
|  |      * | ||||||
|  |      * @return {Promise<any>} Promise resolved when done. | ||||||
|  |      */ | ||||||
|  |     protected fetchMyOverviewTimelineByCourses(): Promise<any> { | ||||||
|  |         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(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										9
									
								
								src/addon/block/timeline/lang/en.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/addon/block/timeline/lang/en.json
									
									
									
									
									
										Normal file
									
								
							| @ -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" | ||||||
|  | } | ||||||
| @ -14,16 +14,16 @@ | |||||||
| 
 | 
 | ||||||
| import { Injectable } from '@angular/core'; | import { Injectable } from '@angular/core'; | ||||||
| import { CoreSitesProvider } from '@providers/sites'; | import { CoreSitesProvider } from '@providers/sites'; | ||||||
| import { CoreSite } from '@classes/site'; |  | ||||||
| import * as moment from 'moment'; | import * as moment from 'moment'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Service that provides some features regarding course overview. |  * Service that provides some features regarding course overview. | ||||||
|  */ |  */ | ||||||
| @Injectable() | @Injectable() | ||||||
| export class CoreCoursesMyOverviewProvider { | export class AddonBlockTimelineProvider { | ||||||
|     static EVENTS_LIMIT = 20; |     static EVENTS_LIMIT = 20; | ||||||
|     static EVENTS_LIMIT_PER_COURSE = 10; |     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:'; |     protected ROOT_CACHE_KEY = 'myoverview:'; | ||||||
| 
 | 
 | ||||||
|     constructor(private sitesProvider: CoreSitesProvider) { } |     constructor(private sitesProvider: CoreSitesProvider) { } | ||||||
| @ -44,7 +44,7 @@ export class CoreCoursesMyOverviewProvider { | |||||||
|                 data: any = { |                 data: any = { | ||||||
|                     timesortfrom: time, |                     timesortfrom: time, | ||||||
|                     courseid: courseId, |                     courseid: courseId, | ||||||
|                     limitnum: CoreCoursesMyOverviewProvider.EVENTS_LIMIT_PER_COURSE |                     limitnum: AddonBlockTimelineProvider.EVENTS_LIMIT_PER_COURSE | ||||||
|                 }, |                 }, | ||||||
|                 preSets = { |                 preSets = { | ||||||
|                     cacheKey: this.getActionEventsByCourseCacheKey(courseId) |                     cacheKey: this.getActionEventsByCourseCacheKey(courseId) | ||||||
| @ -88,7 +88,7 @@ export class CoreCoursesMyOverviewProvider { | |||||||
|                 data = { |                 data = { | ||||||
|                     timesortfrom: time, |                     timesortfrom: time, | ||||||
|                     courseids: courseIds, |                     courseids: courseIds, | ||||||
|                     limitnum: CoreCoursesMyOverviewProvider.EVENTS_LIMIT_PER_COURSE |                     limitnum: AddonBlockTimelineProvider.EVENTS_LIMIT_PER_COURSE | ||||||
|                 }, |                 }, | ||||||
|                 preSets = { |                 preSets = { | ||||||
|                     cacheKey: this.getActionEventsByCoursesCacheKey() |                     cacheKey: this.getActionEventsByCoursesCacheKey() | ||||||
| @ -131,7 +131,7 @@ export class CoreCoursesMyOverviewProvider { | |||||||
|             const time = moment().subtract(14, 'days').unix(), // Check two weeks ago.
 |             const time = moment().subtract(14, 'days').unix(), // Check two weeks ago.
 | ||||||
|                 data: any = { |                 data: any = { | ||||||
|                     timesortfrom: time, |                     timesortfrom: time, | ||||||
|                     limitnum: CoreCoursesMyOverviewProvider.EVENTS_LIMIT |                     limitnum: AddonBlockTimelineProvider.EVENTS_LIMIT | ||||||
|                 }, |                 }, | ||||||
|                 preSets = { |                 preSets = { | ||||||
|                     cacheKey: this.getActionEventsByTimesortCacheKey(afterEventId, data.limitnum), |                     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<boolean>} Promise resolved with true if enabled, resolved with false otherwise. |  | ||||||
|      */ |  | ||||||
|     isEnabled(): Promise<boolean> { |  | ||||||
|         if (!this.isDisabledInSite()) { |  | ||||||
|             return this.isAvailable().catch(() => { |  | ||||||
|                 return false; |  | ||||||
|             }); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         return Promise.resolve(false); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |     /** | ||||||
|      * Handles course events, filtering and treating if more can be loaded. |      * 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 } { |     protected treatCourseEvents(course: any, timeFrom: number): { events: any[], canLoadMore: number } { | ||||||
|         const 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.
 |         // Filter events by time in case it uses cache.
 | ||||||
|         course.events = course.events.filter((element) => { |         course.events = course.events.filter((element) => { | ||||||
							
								
								
									
										47
									
								
								src/addon/block/timeline/timeline.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								src/addon/block/timeline/timeline.module.ts
									
									
									
									
									
										Normal file
									
								
							| @ -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 {} | ||||||
| @ -83,6 +83,8 @@ import { AddonCompetencyModule } from '@addon/competency/competency.module'; | |||||||
| import { AddonCourseCompletionModule } from '@addon/coursecompletion/coursecompletion.module'; | import { AddonCourseCompletionModule } from '@addon/coursecompletion/coursecompletion.module'; | ||||||
| import { AddonUserProfileFieldModule } from '@addon/userprofilefield/userprofilefield.module'; | import { AddonUserProfileFieldModule } from '@addon/userprofilefield/userprofilefield.module'; | ||||||
| import { AddonFilesModule } from '@addon/files/files.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 { AddonModAssignModule } from '@addon/mod/assign/assign.module'; | ||||||
| import { AddonModBookModule } from '@addon/mod/book/book.module'; | import { AddonModBookModule } from '@addon/mod/book/book.module'; | ||||||
| import { AddonModChatModule } from '@addon/mod/chat/chat.module'; | import { AddonModChatModule } from '@addon/mod/chat/chat.module'; | ||||||
| @ -192,6 +194,8 @@ export const CORE_PROVIDERS: any[] = [ | |||||||
|         AddonCourseCompletionModule, |         AddonCourseCompletionModule, | ||||||
|         AddonUserProfileFieldModule, |         AddonUserProfileFieldModule, | ||||||
|         AddonFilesModule, |         AddonFilesModule, | ||||||
|  |         AddonBlockMyOverviewModule, | ||||||
|  |         AddonBlockTimelineModule, | ||||||
|         AddonModAssignModule, |         AddonModAssignModule, | ||||||
|         AddonModBookModule, |         AddonModBookModule, | ||||||
|         AddonModChatModule, |         AddonModChatModule, | ||||||
|  | |||||||
| @ -10,6 +10,20 @@ | |||||||
|     "addon.badges.issuername": "Issuer name", |     "addon.badges.issuername": "Issuer name", | ||||||
|     "addon.badges.nobadges": "There are no badges available.", |     "addon.badges.nobadges": "There are no badges available.", | ||||||
|     "addon.badges.recipientdetails": "Recipient details", |     "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.calendar": "Calendar", | ||||||
|     "addon.calendar.calendarevents": "Calendar events", |     "addon.calendar.calendarevents": "Calendar events", | ||||||
|     "addon.calendar.defaultnotificationtime": "Default notification time", |     "addon.calendar.defaultnotificationtime": "Default notification time", | ||||||
| @ -1135,34 +1149,20 @@ | |||||||
|     "core.courses.errorselfenrol": "An error occurred while self enrolling.", |     "core.courses.errorselfenrol": "An error occurred while self enrolling.", | ||||||
|     "core.courses.filtermycourses": "Filter my courses", |     "core.courses.filtermycourses": "Filter my courses", | ||||||
|     "core.courses.frontpage": "Front page", |     "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.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.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.nocoursesyet": "No courses in this category", | ||||||
|     "core.courses.noevents": "No upcoming activities due", |  | ||||||
|     "core.courses.nosearchresults": "No results", |     "core.courses.nosearchresults": "No results", | ||||||
|     "core.courses.notenroled": "You are not enrolled in this course", |     "core.courses.notenroled": "You are not enrolled in this course", | ||||||
|     "core.courses.notenrollable": "You cannot enrol yourself in this course.", |     "core.courses.notenrollable": "You cannot enrol yourself in this course.", | ||||||
|     "core.courses.password": "Enrolment key", |     "core.courses.password": "Enrolment key", | ||||||
|     "core.courses.past": "Past", |  | ||||||
|     "core.courses.paymentrequired": "This course requires a payment for entry.", |     "core.courses.paymentrequired": "This course requires a payment for entry.", | ||||||
|     "core.courses.paypalaccepted": "PayPal payments accepted", |     "core.courses.paypalaccepted": "PayPal payments accepted", | ||||||
|     "core.courses.recentlyoverdue": "Recently overdue", |  | ||||||
|     "core.courses.search": "Search", |     "core.courses.search": "Search", | ||||||
|     "core.courses.searchcourses": "Search courses", |     "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.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.selfenrolment": "Self enrolment", | ||||||
|     "core.courses.sendpaymentbutton": "Send payment via PayPal", |     "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.timeline": "Timeline", | ||||||
|     "core.courses.totalcoursesearchresults": "Total courses: {{$a}}", |     "core.courses.totalcoursesearchresults": "Total courses: {{$a}}", | ||||||
|     "core.currentdevice": "Current device", |     "core.currentdevice": "Current device", | ||||||
|  | |||||||
| @ -21,13 +21,11 @@ import { CoreDirectivesModule } from '@directives/directives.module'; | |||||||
| import { CorePipesModule } from '@pipes/pipes.module'; | import { CorePipesModule } from '@pipes/pipes.module'; | ||||||
| import { CoreCoursesCourseProgressComponent } from '../components/course-progress/course-progress'; | import { CoreCoursesCourseProgressComponent } from '../components/course-progress/course-progress'; | ||||||
| import { CoreCoursesCourseListItemComponent } from '../components/course-list-item/course-list-item'; | import { CoreCoursesCourseListItemComponent } from '../components/course-list-item/course-list-item'; | ||||||
| import { CoreCoursesOverviewEventsComponent } from '../components/overview-events/overview-events'; |  | ||||||
| 
 | 
 | ||||||
| @NgModule({ | @NgModule({ | ||||||
|     declarations: [ |     declarations: [ | ||||||
|         CoreCoursesCourseProgressComponent, |         CoreCoursesCourseProgressComponent, | ||||||
|         CoreCoursesCourseListItemComponent, |         CoreCoursesCourseListItemComponent | ||||||
|         CoreCoursesOverviewEventsComponent |  | ||||||
|     ], |     ], | ||||||
|     imports: [ |     imports: [ | ||||||
|         CommonModule, |         CommonModule, | ||||||
| @ -41,8 +39,7 @@ import { CoreCoursesOverviewEventsComponent } from '../components/overview-event | |||||||
|     ], |     ], | ||||||
|     exports: [ |     exports: [ | ||||||
|         CoreCoursesCourseProgressComponent, |         CoreCoursesCourseProgressComponent, | ||||||
|         CoreCoursesCourseListItemComponent, |         CoreCoursesCourseListItemComponent | ||||||
|         CoreCoursesOverviewEventsComponent |  | ||||||
|     ] |     ] | ||||||
| }) | }) | ||||||
| export class CoreCoursesComponentsModule {} | export class CoreCoursesComponentsModule {} | ||||||
|  | |||||||
| @ -16,17 +16,17 @@ import { NgModule } from '@angular/core'; | |||||||
| import { CoreCoursesProvider } from './providers/courses'; | import { CoreCoursesProvider } from './providers/courses'; | ||||||
| import { CoreCoursesHelperProvider } from './providers/helper'; | import { CoreCoursesHelperProvider } from './providers/helper'; | ||||||
| import { CoreCoursesMainMenuHandler } from './providers/mainmenu-handler'; | 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 { CoreCoursesCourseLinkHandler } from './providers/course-link-handler'; | ||||||
| import { CoreCoursesIndexLinkHandler } from './providers/courses-index-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 { CoreMainMenuDelegate } from '@core/mainmenu/providers/delegate'; | ||||||
| import { CoreContentLinksDelegate } from '@core/contentlinks/providers/delegate'; | import { CoreContentLinksDelegate } from '@core/contentlinks/providers/delegate'; | ||||||
| 
 | 
 | ||||||
| // List of providers (without handlers).
 | // List of providers (without handlers).
 | ||||||
| export const CORE_COURSES_PROVIDERS: any[] = [ | export const CORE_COURSES_PROVIDERS: any[] = [ | ||||||
|     CoreCoursesProvider, |     CoreCoursesProvider, | ||||||
|     CoreCoursesMyOverviewProvider, |     CoreCoursesDashboardProvider, | ||||||
|     CoreCoursesHelperProvider |     CoreCoursesHelperProvider | ||||||
| ]; | ]; | ||||||
| 
 | 
 | ||||||
| @ -36,23 +36,23 @@ export const CORE_COURSES_PROVIDERS: any[] = [ | |||||||
|     ], |     ], | ||||||
|     providers: [ |     providers: [ | ||||||
|         CoreCoursesProvider, |         CoreCoursesProvider, | ||||||
|         CoreCoursesMyOverviewProvider, |         CoreCoursesDashboardProvider, | ||||||
|         CoreCoursesHelperProvider, |         CoreCoursesHelperProvider, | ||||||
|         CoreCoursesMainMenuHandler, |         CoreCoursesMainMenuHandler, | ||||||
|         CoreCoursesCourseLinkHandler, |         CoreCoursesCourseLinkHandler, | ||||||
|         CoreCoursesIndexLinkHandler, |         CoreCoursesIndexLinkHandler, | ||||||
|         CoreCoursesMyOverviewLinkHandler |         CoreCoursesDashboardLinkHandler | ||||||
|     ], |     ], | ||||||
|     exports: [] |     exports: [] | ||||||
| }) | }) | ||||||
| export class CoreCoursesModule { | export class CoreCoursesModule { | ||||||
|     constructor(mainMenuDelegate: CoreMainMenuDelegate, contentLinksDelegate: CoreContentLinksDelegate, |     constructor(mainMenuDelegate: CoreMainMenuDelegate, contentLinksDelegate: CoreContentLinksDelegate, | ||||||
|             mainMenuHandler: CoreCoursesMainMenuHandler, courseLinkHandler: CoreCoursesCourseLinkHandler, |             mainMenuHandler: CoreCoursesMainMenuHandler, courseLinkHandler: CoreCoursesCourseLinkHandler, | ||||||
|             indexLinkHandler: CoreCoursesIndexLinkHandler, myOverviewLinkHandler: CoreCoursesMyOverviewLinkHandler) { |             indexLinkHandler: CoreCoursesIndexLinkHandler, dashboardLinkHandler: CoreCoursesDashboardLinkHandler) { | ||||||
|         mainMenuDelegate.registerHandler(mainMenuHandler); |         mainMenuDelegate.registerHandler(mainMenuHandler); | ||||||
| 
 | 
 | ||||||
|         contentLinksDelegate.registerHandler(courseLinkHandler); |         contentLinksDelegate.registerHandler(courseLinkHandler); | ||||||
|         contentLinksDelegate.registerHandler(indexLinkHandler); |         contentLinksDelegate.registerHandler(indexLinkHandler); | ||||||
|         contentLinksDelegate.registerHandler(myOverviewLinkHandler); |         contentLinksDelegate.registerHandler(dashboardLinkHandler); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -14,34 +14,20 @@ | |||||||
|     "errorselfenrol": "An error occurred while self enrolling.", |     "errorselfenrol": "An error occurred while self enrolling.", | ||||||
|     "filtermycourses": "Filter my courses", |     "filtermycourses": "Filter my courses", | ||||||
|     "frontpage": "Front page", |     "frontpage": "Front page", | ||||||
|     "future": "Future", |  | ||||||
|     "inprogress": "In progress", |  | ||||||
|     "morecourses": "More courses", |  | ||||||
|     "mycourses": "My courses", |     "mycourses": "My courses", | ||||||
|     "next30days": "Next 30 days", |  | ||||||
|     "next7days": "Next 7 days", |  | ||||||
|     "nocourses": "No course information to show.", |     "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", |     "nocoursesyet": "No courses in this category", | ||||||
|     "noevents": "No upcoming activities due", |  | ||||||
|     "nosearchresults": "No results", |     "nosearchresults": "No results", | ||||||
|     "notenroled": "You are not enrolled in this course", |     "notenroled": "You are not enrolled in this course", | ||||||
|     "notenrollable": "You cannot enrol yourself in this course.", |     "notenrollable": "You cannot enrol yourself in this course.", | ||||||
|     "password": "Enrolment key", |     "password": "Enrolment key", | ||||||
|     "past": "Past", |  | ||||||
|     "paymentrequired": "This course requires a payment for entry.", |     "paymentrequired": "This course requires a payment for entry.", | ||||||
|     "paypalaccepted": "PayPal payments accepted", |     "paypalaccepted": "PayPal payments accepted", | ||||||
|     "recentlyoverdue": "Recently overdue", |  | ||||||
|     "search": "Search", |     "search": "Search", | ||||||
|     "searchcourses": "Search courses", |     "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.", |     "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", |     "selfenrolment": "Self enrolment", | ||||||
|     "sendpaymentbutton": "Send payment via PayPal", |     "sendpaymentbutton": "Send payment via PayPal", | ||||||
|     "sortbycourses": "Sort by courses", |  | ||||||
|     "sortbydates": "Sort by dates", |  | ||||||
|     "timeline": "Timeline", |     "timeline": "Timeline", | ||||||
|     "totalcoursesearchresults": "Total courses: {{$a}}" |     "totalcoursesearchresults": "Total courses: {{$a}}" | ||||||
| } | } | ||||||
							
								
								
									
										50
									
								
								src/core/courses/pages/dashboard/dashboard.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								src/core/courses/pages/dashboard/dashboard.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,50 @@ | |||||||
|  | <ion-header> | ||||||
|  |     <ion-navbar core-back-button> | ||||||
|  |         <ion-title><core-format-text [text]="siteName"></core-format-text></ion-title> | ||||||
|  | 
 | ||||||
|  |         <ion-buttons end> | ||||||
|  |             <button *ngIf="searchEnabled" ion-button icon-only (click)="openSearch()" [attr.aria-label]="'core.courses.searchcourses' | translate"> | ||||||
|  |                 <ion-icon name="search"></ion-icon> | ||||||
|  |             </button> | ||||||
|  |         </ion-buttons> | ||||||
|  |     </ion-navbar> | ||||||
|  | </ion-header> | ||||||
|  | <ion-content> | ||||||
|  |     <core-tabs [selectedIndex]="firstSelectedTab" [hideUntil]="tabsReady"> | ||||||
|  |         <!-- Site home tab. --> | ||||||
|  |         <core-tab [show]="siteHomeEnabled" [title]="'core.sitehome.sitehome' | translate" (ionSelect)="tabChanged('sitehome')"> | ||||||
|  |             <ng-template> | ||||||
|  |                 <ion-content> | ||||||
|  |                     <ion-refresher [enabled]="!!siteHomeComponent && siteHomeComponent.dataLoaded" (ionRefresh)="siteHomeComponent.doRefresh($event)"> | ||||||
|  |                         <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content> | ||||||
|  |                     </ion-refresher> | ||||||
|  |                     <core-sitehome-index></core-sitehome-index> | ||||||
|  |                 </ion-content> | ||||||
|  |             </ng-template> | ||||||
|  |         </core-tab> | ||||||
|  | 
 | ||||||
|  |         <!-- Courses tab. --> | ||||||
|  |         <core-tab [title]="'core.courses.courses' | translate" (ionSelect)="tabChanged('courses')"> | ||||||
|  |             <ng-template> | ||||||
|  |                 <ion-content> | ||||||
|  |                     <ion-refresher [enabled]="!!blockMyOverview && blockMyOverview.loaded" (ionRefresh)="blockMyOverview.doRefresh($event)"> | ||||||
|  |                         <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content> | ||||||
|  |                     </ion-refresher> | ||||||
|  |                     <addon-block-myoverview></addon-block-myoverview> | ||||||
|  |                 </ion-content> | ||||||
|  |             </ng-template> | ||||||
|  |         </core-tab> | ||||||
|  | 
 | ||||||
|  |         <!-- Timeline tab. --> | ||||||
|  |         <core-tab [title]="'core.courses.timeline' | translate" (ionSelect)="tabChanged('timeline')"> | ||||||
|  |             <ng-template> | ||||||
|  |                 <ion-content> | ||||||
|  |                     <ion-refresher [enabled]="!!blockTimeline && blockTimeline.loaded" (ionRefresh)="blockTimeline.doRefresh($event)"> | ||||||
|  |                         <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content> | ||||||
|  |                     </ion-refresher> | ||||||
|  |                     <addon-block-timeline></addon-block-timeline> | ||||||
|  |                 </ion-content> | ||||||
|  |             </ng-template> | ||||||
|  |         </core-tab> | ||||||
|  |     </core-tabs> | ||||||
|  | </ion-content> | ||||||
| @ -15,23 +15,27 @@ | |||||||
| import { NgModule } from '@angular/core'; | import { NgModule } from '@angular/core'; | ||||||
| import { IonicPageModule } from 'ionic-angular'; | import { IonicPageModule } from 'ionic-angular'; | ||||||
| import { TranslateModule } from '@ngx-translate/core'; | import { TranslateModule } from '@ngx-translate/core'; | ||||||
| import { CoreCoursesMyOverviewPage } from './my-overview'; | import { CoreCoursesDashboardPage } from './dashboard'; | ||||||
| import { CoreComponentsModule } from '@components/components.module'; | import { CoreComponentsModule } from '@components/components.module'; | ||||||
| import { CoreDirectivesModule } from '@directives/directives.module'; | import { CoreDirectivesModule } from '@directives/directives.module'; | ||||||
| import { CoreCoursesComponentsModule } from '../../components/components.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'; | import { CoreSiteHomeComponentsModule } from '@core/sitehome/components/components.module'; | ||||||
| 
 | 
 | ||||||
| @NgModule({ | @NgModule({ | ||||||
|     declarations: [ |     declarations: [ | ||||||
|         CoreCoursesMyOverviewPage, |         CoreCoursesDashboardPage, | ||||||
|     ], |     ], | ||||||
|     imports: [ |     imports: [ | ||||||
|         CoreComponentsModule, |         CoreComponentsModule, | ||||||
|         CoreDirectivesModule, |         CoreDirectivesModule, | ||||||
|         CoreCoursesComponentsModule, |         CoreCoursesComponentsModule, | ||||||
|         CoreSiteHomeComponentsModule, |         CoreSiteHomeComponentsModule, | ||||||
|         IonicPageModule.forChild(CoreCoursesMyOverviewPage), |         AddonBlockMyOverviewModule, | ||||||
|  |         AddonBlockTimelineModule, | ||||||
|  |         IonicPageModule.forChild(CoreCoursesDashboardPage), | ||||||
|         TranslateModule.forChild() |         TranslateModule.forChild() | ||||||
|     ], |     ], | ||||||
| }) | }) | ||||||
| export class CoreCoursesMyOverviewPageModule {} | export class CoreCoursesDashboardPageModule {} | ||||||
| @ -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 { |     ion-badge.core-course-download-courses-progress { | ||||||
|         display: block; |         display: block; | ||||||
|         @include float(start); |         @include float(start); | ||||||
							
								
								
									
										125
									
								
								src/core/courses/pages/dashboard/dashboard.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								src/core/courses/pages/dashboard/dashboard.ts
									
									
									
									
									
										Normal file
									
								
							| @ -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(); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -1,109 +0,0 @@ | |||||||
| <ion-header> |  | ||||||
|     <ion-navbar core-back-button> |  | ||||||
|         <ion-title><core-format-text [text]="siteName"></core-format-text></ion-title> |  | ||||||
| 
 |  | ||||||
|         <ion-buttons end> |  | ||||||
|             <button *ngIf="tabShown == 'courses' && courses[courses.selected] && courses[courses.selected].length > 5" ion-button icon-only [attr.aria-label]="'core.courses.filtermycourses' | translate" (click)="switchFilter()"> |  | ||||||
|                 <ion-icon name="funnel"></ion-icon> |  | ||||||
|             </button> |  | ||||||
|             <button *ngIf="searchEnabled" ion-button icon-only (click)="openSearch()" [attr.aria-label]="'core.courses.searchcourses' | translate"> |  | ||||||
|                 <ion-icon name="search"></ion-icon> |  | ||||||
|             </button> |  | ||||||
|         </ion-buttons> |  | ||||||
|     </ion-navbar> |  | ||||||
| </ion-header> |  | ||||||
| <ion-content> |  | ||||||
|     <core-tabs [selectedIndex]="firstSelectedTab" [hideUntil]="tabsReady"> |  | ||||||
|         <!-- Site home tab. --> |  | ||||||
|         <core-tab [show]="siteHomeEnabled" [title]="'core.sitehome.sitehome' | translate" (ionSelect)="tabChanged('sitehome')"> |  | ||||||
|             <ng-template> |  | ||||||
|                 <ion-content> |  | ||||||
|                     <ion-refresher [enabled]="siteHomeComponent && siteHomeComponent.dataLoaded" (ionRefresh)="siteHomeComponent.doRefresh($event)"> |  | ||||||
|                         <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content> |  | ||||||
|                     </ion-refresher> |  | ||||||
|                     <core-sitehome-index></core-sitehome-index> |  | ||||||
|                 </ion-content> |  | ||||||
|             </ng-template> |  | ||||||
|         </core-tab> |  | ||||||
| 
 |  | ||||||
|         <!-- Courses tab. --> |  | ||||||
|         <core-tab [title]="'core.courses.courses' | translate" (ionSelect)="tabChanged('courses')"> |  | ||||||
|             <ng-template> |  | ||||||
|                 <ion-content> |  | ||||||
|                     <ion-refresher [enabled]="timeline.loaded || timelineCourses.loaded || courses.loaded" (ionRefresh)="refreshMyOverview($event)"> |  | ||||||
|                         <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content> |  | ||||||
|                     </ion-refresher> |  | ||||||
| 
 |  | ||||||
|                     <core-loading [hideUntil]="courses.loaded" class="core-loading-center"> |  | ||||||
|                         <!-- "Time" selector. --> |  | ||||||
|                         <div padding class="clearfix" [hidden]="showFilter" ion-row justify-content-between> |  | ||||||
|                             <ion-select float-start [title]="'core.show' | translate" [(ngModel)]="courses.selected" ion-col (ngModelChange)="selectedChanged()" interface="popover" class="core-button-select"> |  | ||||||
|                                 <ion-option value="inprogress">{{ 'core.courses.inprogress' | translate }}</ion-option> |  | ||||||
|                                 <ion-option value="future">{{ 'core.courses.future' | translate }}</ion-option> |  | ||||||
|                                 <ion-option value="past">{{ 'core.courses.past' | translate }}</ion-option> |  | ||||||
|                             </ion-select> |  | ||||||
|                             <!-- Download all courses. --> |  | ||||||
|                             <div *ngIf="downloadAllCoursesEnabled && courses[courses.selected] && courses[courses.selected].length > 1" class="core-button-spinner"> |  | ||||||
|                                 <button *ngIf="prefetchCoursesData[courses.selected].icon && prefetchCoursesData[courses.selected].icon != 'spinner'" ion-button icon-only clear color="dark" (click)="prefetchCourses()"> |  | ||||||
|                                     <core-icon [name]="prefetchCoursesData[courses.selected].icon"></core-icon> |  | ||||||
|                                 </button> |  | ||||||
|                                 <ion-badge class="core-course-download-courses-progress" *ngIf="prefetchCoursesData[courses.selected].badge">{{prefetchCoursesData[courses.selected].badge}}</ion-badge> |  | ||||||
|                                 <ion-spinner *ngIf="!prefetchCoursesData[courses.selected].icon || prefetchCoursesData[courses.selected].icon == 'spinner'"></ion-spinner> |  | ||||||
|                             </div> |  | ||||||
|                         </div> |  | ||||||
|                         <core-empty-box *ngIf="courses[courses.selected].length == 0 && courses.selected == 'inprogress'" image="assets/img/icons/courses.svg" [message]="'core.courses.nocoursesinprogress' | translate"></core-empty-box> |  | ||||||
|                         <core-empty-box *ngIf="courses[courses.selected].length == 0 && courses.selected == 'future'" image="assets/img/icons/courses.svg" [message]="'core.courses.nocoursesfuture' | translate"></core-empty-box> |  | ||||||
|                         <core-empty-box *ngIf="courses[courses.selected].length == 0 && courses.selected == 'past'" image="assets/img/icons/courses.svg" [message]="'core.courses.nocoursespast' | translate"></core-empty-box> |  | ||||||
| 
 |  | ||||||
|                         <!-- Filter courses. --> |  | ||||||
|                         <ion-searchbar #searchbar *ngIf="showFilter" [(ngModel)]="courses.filter" (ionInput)="filterChanged($event)" (ionCancel)="filterChanged()" [placeholder]="'core.courses.filtermycourses' | translate"> |  | ||||||
|                         </ion-searchbar> |  | ||||||
|                         <!-- List of courses. --> |  | ||||||
|                         <div> |  | ||||||
|                             <ion-grid no-padding> |  | ||||||
|                                 <ion-row no-padding> |  | ||||||
|                                     <ion-col *ngFor="let course of filteredCourses" no-padding col-12 col-sm-6 col-md-6 col-lg-4 col-xl-4 align-self-stretch> |  | ||||||
|                                         <core-courses-course-progress [course]="course" class="core-courseoverview"></core-courses-course-progress> |  | ||||||
|                                     </ion-col> |  | ||||||
|                                 </ion-row> |  | ||||||
|                             </ion-grid> |  | ||||||
|                         </div> |  | ||||||
|                     </core-loading> |  | ||||||
|                 </ion-content> |  | ||||||
|             </ng-template> |  | ||||||
|         </core-tab> |  | ||||||
| 
 |  | ||||||
|         <!-- Timeline tab. --> |  | ||||||
|         <core-tab [title]="'core.courses.timeline' | translate" (ionSelect)="tabChanged('timeline')"> |  | ||||||
|             <ng-template> |  | ||||||
|                 <ion-content> |  | ||||||
|                     <ion-refresher [enabled]="timeline.loaded || timelineCourses.loaded || courses.loaded" (ionRefresh)="refreshMyOverview($event)"> |  | ||||||
|                         <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content> |  | ||||||
|                     </ion-refresher> |  | ||||||
| 
 |  | ||||||
|                     <div padding [hidden]="!(timeline.loaded || timelineCourses.loaded)"> |  | ||||||
|                         <ion-select [(ngModel)]="timeline.sort" (ngModelChange)="switchSort()" interface="popover" class="core-button-select"> |  | ||||||
|                             <ion-option value="sortbydates">{{ 'core.courses.sortbydates' | translate }}</ion-option> |  | ||||||
|                             <ion-option value="sortbycourses">{{ 'core.courses.sortbycourses' | translate }}</ion-option> |  | ||||||
|                         </ion-select> |  | ||||||
|                     </div> |  | ||||||
|                     <core-loading [hideUntil]="timeline.loaded" [hidden]="timeline.sort != 'sortbydates'" class="core-loading-center"> |  | ||||||
|                         <core-courses-overview-events [events]="timeline.events" showCourse="true" [canLoadMore]="timeline.canLoadMore" (loadMore)="loadMoreTimeline()"></core-courses-overview-events> |  | ||||||
|                     </core-loading> |  | ||||||
|                     <core-loading [hideUntil]="timelineCourses.loaded" [hidden]="timeline.sort != 'sortbycourses'" class="core-loading-center"> |  | ||||||
|                         <ion-grid no-padding> |  | ||||||
|                             <ion-row no-padding> |  | ||||||
|                                 <ion-col *ngFor="let course of timelineCourses.courses" no-padding col-12 col-md-6> |  | ||||||
|                                     <core-courses-course-progress [course]="course"> |  | ||||||
|                                         <core-courses-overview-events [events]="course.events" [canLoadMore]="course.canLoadMore" (loadMore)="loadMoreCourse(course)"></core-courses-overview-events> |  | ||||||
|                                     </core-courses-course-progress> |  | ||||||
|                                 </ion-col> |  | ||||||
|                             </ion-row> |  | ||||||
|                         </ion-grid> |  | ||||||
|                         <core-empty-box *ngIf="timelineCourses.courses.length == 0" image="assets/img/icons/courses.svg" [message]="'core.courses.nocoursesoverview' | translate"></core-empty-box> |  | ||||||
|                     </core-loading> |  | ||||||
|                 </ion-content> |  | ||||||
|             </ng-template> |  | ||||||
|         </core-tab> |  | ||||||
|     </core-tabs> |  | ||||||
| </ion-content> |  | ||||||
| @ -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<any>} Promise resolved when done. |  | ||||||
|      */ |  | ||||||
|     protected fetchMyOverviewTimeline(afterEventId?: number): Promise<any> { |  | ||||||
|         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<any>} Promise resolved when done. |  | ||||||
|      */ |  | ||||||
|     protected fetchMyOverviewTimelineByCourses(): Promise<any> { |  | ||||||
|         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<any>} Promise resolved when done. |  | ||||||
|      */ |  | ||||||
|     protected fetchMyOverviewCourses(): Promise<any> { |  | ||||||
|         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<any[]>} Promise resolved when done. |  | ||||||
|      */ |  | ||||||
|     protected fetchUserCourses(): Promise<any[]> { |  | ||||||
|         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<any>} Promise resolved when done. |  | ||||||
|      */ |  | ||||||
|     refreshMyOverview(refresher: any): Promise<any> { |  | ||||||
|         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<any> { |  | ||||||
|         return this.fetchMyOverviewTimeline(this.timeline.canLoadMore); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Load more events. |  | ||||||
|      * |  | ||||||
|      * @param {any} course Course. |  | ||||||
|      * @return {Promise<any>} Promise resolved when done. |  | ||||||
|      */ |  | ||||||
|     loadMoreCourse(course: any): Promise<any> { |  | ||||||
|         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<any>} Promise resolved when done. |  | ||||||
|      */ |  | ||||||
|     prefetchCourses(): Promise<any> { |  | ||||||
|         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(); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @ -21,7 +21,7 @@ import { CoreLoginHelperProvider } from '@core/login/providers/helper'; | |||||||
|  * Handler to treat links to my overview. |  * Handler to treat links to my overview. | ||||||
|  */ |  */ | ||||||
| @Injectable() | @Injectable() | ||||||
| export class CoreCoursesMyOverviewLinkHandler extends CoreContentLinksHandlerBase { | export class CoreCoursesDashboardLinkHandler extends CoreContentLinksHandlerBase { | ||||||
|     name = 'CoreCoursesMyOverviewLinkHandler'; |     name = 'CoreCoursesMyOverviewLinkHandler'; | ||||||
|     featureName = 'CoreMainMenuDelegate_CoreCourses'; |     featureName = 'CoreMainMenuDelegate_CoreCourses'; | ||||||
|     pattern = /\/my\/?$/; |     pattern = /\/my\/?$/; | ||||||
| @ -44,7 +44,7 @@ export class CoreCoursesMyOverviewLinkHandler extends CoreContentLinksHandlerBas | |||||||
|         return [{ |         return [{ | ||||||
|             action: (siteId, navCtrl?): void => { |             action: (siteId, navCtrl?): void => { | ||||||
|                 // Always use redirect to make it the new history root (to avoid "loops" in history).
 |                 // 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); | ||||||
|             } |             } | ||||||
|         }]; |         }]; | ||||||
|     } |     } | ||||||
							
								
								
									
										64
									
								
								src/core/courses/providers/dashboard.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								src/core/courses/providers/dashboard.ts
									
									
									
									
									
										Normal file
									
								
							| @ -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<boolean>} Promise resolved with true if available, resolved with false or rejected otherwise. | ||||||
|  |      */ | ||||||
|  |     isAvailable(siteId?: string): Promise<boolean> { | ||||||
|  |         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<boolean>} Promise resolved with true if enabled, resolved with false otherwise. | ||||||
|  |      */ | ||||||
|  |     isEnabled(): Promise<boolean> { | ||||||
|  |         if (!this.isDisabledInSite()) { | ||||||
|  |             return this.isAvailable().catch(() => { | ||||||
|  |                 return false; | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return Promise.resolve(false); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -72,4 +72,39 @@ export class CoreCoursesHelperProvider { | |||||||
|             }); |             }); | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Get user courses with admin and nav options. | ||||||
|  |      * | ||||||
|  |      * @return {Promise<any[]>} Promise resolved when done. | ||||||
|  |      */ | ||||||
|  |     getUserCoursesWithOptions(): Promise<any[]> { | ||||||
|  |         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); | ||||||
|  |                 }); | ||||||
|  |             }); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -15,7 +15,7 @@ | |||||||
| import { Injectable } from '@angular/core'; | import { Injectable } from '@angular/core'; | ||||||
| import { CoreCoursesProvider } from './courses'; | import { CoreCoursesProvider } from './courses'; | ||||||
| import { CoreMainMenuHandler, CoreMainMenuHandlerData } from '@core/mainmenu/providers/delegate'; | 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. |  * 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 { | export class CoreCoursesMainMenuHandler implements CoreMainMenuHandler { | ||||||
|     name = 'CoreCourses'; |     name = 'CoreCourses'; | ||||||
|     priority = 1100; |     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. |      * Check if the handler is enabled on a site level. | ||||||
| @ -35,8 +35,8 @@ export class CoreCoursesMainMenuHandler implements CoreMainMenuHandler { | |||||||
|      */ |      */ | ||||||
|     isEnabled(): boolean | Promise<boolean> { |     isEnabled(): boolean | Promise<boolean> { | ||||||
|         // Check if my overview is enabled.
 |         // Check if my overview is enabled.
 | ||||||
|         return this.myOverviewProvider.isEnabled().then((enabled) => { |         return this.dashboardProvider.isEnabled().then((enabled) => { | ||||||
|             this.isOverviewEnabled = enabled; |             this.isDashboardEnabled = enabled; | ||||||
|             if (enabled) { |             if (enabled) { | ||||||
|                 return true; |                 return true; | ||||||
|             } |             } | ||||||
| @ -52,11 +52,11 @@ export class CoreCoursesMainMenuHandler implements CoreMainMenuHandler { | |||||||
|      * @return {CoreMainMenuHandlerData} Data needed to render the handler. |      * @return {CoreMainMenuHandlerData} Data needed to render the handler. | ||||||
|      */ |      */ | ||||||
|     getDisplayData(): CoreMainMenuHandlerData { |     getDisplayData(): CoreMainMenuHandlerData { | ||||||
|         if (this.isOverviewEnabled) { |         if (this.isDashboardEnabled) { | ||||||
|             return { |             return { | ||||||
|                 icon: 'home', |                 icon: 'home', | ||||||
|                 title: 'core.courses.courseoverview', |                 title: 'core.courses.courseoverview', | ||||||
|                 page: 'CoreCoursesMyOverviewPage', |                 page: 'CoreCoursesDashboardPage', | ||||||
|                 class: 'core-courseoverview-handler' |                 class: 'core-courseoverview-handler' | ||||||
|             }; |             }; | ||||||
|         } else { |         } else { | ||||||
|  | |||||||
| @ -15,7 +15,7 @@ | |||||||
| import { Injectable } from '@angular/core'; | import { Injectable } from '@angular/core'; | ||||||
| import { CoreSiteHomeProvider } from './sitehome'; | import { CoreSiteHomeProvider } from './sitehome'; | ||||||
| import { CoreMainMenuHandler, CoreMainMenuHandlerData } from '@core/mainmenu/providers/delegate'; | 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. |  * 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 { | export class CoreSiteHomeMainMenuHandler implements CoreMainMenuHandler { | ||||||
|     name = 'CoreSiteHome'; |     name = 'CoreSiteHome'; | ||||||
|     priority = 1200; |     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. |      * Check if the handler is enabled on a site level. | ||||||
| @ -35,7 +34,7 @@ export class CoreSiteHomeMainMenuHandler implements CoreMainMenuHandler { | |||||||
|      */ |      */ | ||||||
|     isEnabled(): boolean | Promise<boolean> { |     isEnabled(): boolean | Promise<boolean> { | ||||||
|         // Check if my overview is enabled.
 |         // Check if my overview is enabled.
 | ||||||
|         return this.myOverviewProvider.isEnabled().then((enabled) => { |         return this.dashboardProvider.isEnabled().then((enabled) => { | ||||||
|             if (enabled) { |             if (enabled) { | ||||||
|                 // My overview is enabled, Site Home will be inside the overview page.
 |                 // My overview is enabled, Site Home will be inside the overview page.
 | ||||||
|                 return false; |                 return false; | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user