forked from EVOgeek/Vmeda.Online
		
	
						commit
						f0c544df03
					
				| @ -27,7 +27,6 @@ import { CoreDomUtils } from '@services/utils/dom'; | ||||
| import { CoreTextUtils } from '@services/utils/text'; | ||||
| import { AddonCourseCompletion } from '@addons/coursecompletion/services/coursecompletion'; | ||||
| import { IonSearchbar } from '@ionic/angular'; | ||||
| import moment from 'moment'; | ||||
| import { CoreNavigator } from '@services/navigator'; | ||||
| 
 | ||||
| const FILTER_PRIORITY: AddonBlockMyOverviewTimeFilters[] = | ||||
| @ -478,13 +477,19 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem | ||||
|                     break; | ||||
|                 case 'inprogress': | ||||
|                     this.filteredCourses = this.filteredCourses.filter((course) => | ||||
|                         !course.hidden && !this.isPastCourse(course) && !this.isFutureCourse(course)); | ||||
|                         !course.hidden && | ||||
|                         !CoreCoursesHelper.isPastCourse(course, this.gradePeriodAfter) && | ||||
|                         !CoreCoursesHelper.isFutureCourse(course, this.gradePeriodAfter, this.gradePeriodBefore)); | ||||
|                     break; | ||||
|                 case 'future': | ||||
|                     this.filteredCourses = this.filteredCourses.filter((course) => !course.hidden && this.isFutureCourse(course)); | ||||
|                     this.filteredCourses = this.filteredCourses.filter((course) => | ||||
|                         !course.hidden && | ||||
|                         CoreCoursesHelper.isFutureCourse(course, this.gradePeriodAfter, this.gradePeriodBefore)); | ||||
|                     break; | ||||
|                 case 'past': | ||||
|                     this.filteredCourses = this.filteredCourses.filter((course) => !course.hidden && this.isPastCourse(course)); | ||||
|                     this.filteredCourses = this.filteredCourses.filter((course) => | ||||
|                         !course.hidden && | ||||
|                         CoreCoursesHelper.isPastCourse(course, this.gradePeriodAfter)); | ||||
|                     break; | ||||
|                 case 'favourite': | ||||
|                     this.filteredCourses = this.filteredCourses.filter((course) => !course.hidden && course.isfavourite); | ||||
| @ -515,44 +520,6 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem | ||||
|         this.initPrefetchCoursesIcons(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Calculates if course date is past. | ||||
|      * | ||||
|      * @param course Course Object. | ||||
|      * @return Wether the course is past. | ||||
|      */ | ||||
|     protected isPastCourse(course: CoreEnrolledCourseDataWithOptions): boolean { | ||||
|         if (course.completed) { | ||||
|             return true; | ||||
|         } | ||||
| 
 | ||||
|         if (!course.enddate) { | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         // Calculate the end date to use for display classification purposes, incorporating the grace period, if any.
 | ||||
|         const endDate = moment(course.enddate * 1000).add(this.gradePeriodAfter, 'days').valueOf(); | ||||
| 
 | ||||
|         return endDate < this.today; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Calculates if course date is future. | ||||
|      * | ||||
|      * @param course Course Object. | ||||
|      * @return Wether the course is future. | ||||
|      */ | ||||
|     protected isFutureCourse(course: CoreEnrolledCourseDataWithOptions): boolean { | ||||
|         if (this.isPastCourse(course) || !course.startdate) { | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         // Calculate the start date to use for display classification purposes, incorporating the grace period, if any.
 | ||||
|         const startDate = moment(course.startdate * 1000).subtract(this.gradePeriodBefore, 'days').valueOf(); | ||||
| 
 | ||||
|         return startDate > this.today; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Sort courses | ||||
|      * | ||||
|  | ||||
| @ -18,10 +18,10 @@ import { CoreDomUtils } from '@services/utils/dom'; | ||||
| import { CoreTextUtils } from '@services/utils/text'; | ||||
| import { CoreTimeUtils } from '@services/utils/time'; | ||||
| import { CoreCourse } from '@features/course/services/course'; | ||||
| import moment from 'moment'; | ||||
| import { CoreContentLinksHelper } from '@features/contentlinks/services/contentlinks-helper'; | ||||
| import { AddonCalendarEvent } from '@addons/calendar/services/calendar'; | ||||
| import { CoreEnrolledCourseDataWithOptions } from '@features/courses/services/courses-helper'; | ||||
| import { AddonBlockTimeline } from '../../services/timeline'; | ||||
| 
 | ||||
| /** | ||||
|  * Directive to render a list of events in course overview. | ||||
| @ -37,6 +37,7 @@ export class AddonBlockTimelineEventsComponent implements OnChanges { | ||||
|     @Input() course?: CoreEnrolledCourseDataWithOptions; // Whether to show the course name.
 | ||||
|     @Input() from = 0; // Number of days from today to offset the events.
 | ||||
|     @Input() to?: number; // Number of days from today to limit the events to. If not defined, no limit.
 | ||||
|     @Input() overdue = false; // If filtering overdue events or not.
 | ||||
|     @Input() canLoadMore = false; // Whether more events can be loaded.
 | ||||
|     @Output() loadMore = new EventEmitter(); // Notify that more events should be loaded.
 | ||||
| 
 | ||||
| @ -53,18 +54,13 @@ export class AddonBlockTimelineEventsComponent implements OnChanges { | ||||
| 
 | ||||
|         if (changes.events || changes.from || changes.to) { | ||||
|             if (this.events) { | ||||
|                 const filteredEvents = await this.filterEventsByTime(this.from, this.to); | ||||
|                 const filteredEvents = await this.filterEventsByTime(); | ||||
|                 this.empty = !filteredEvents || filteredEvents.length <= 0; | ||||
| 
 | ||||
|                 const now = CoreTimeUtils.timestamp(); | ||||
| 
 | ||||
|                 const eventsByDay: Record<number, AddonCalendarEvent[]> = {}; | ||||
|                 const eventsByDay: Record<number, AddonBlockTimelineEvent[]> = {}; | ||||
|                 filteredEvents.forEach((event) => { | ||||
|                     const dayTimestamp = CoreTimeUtils.getMidnightForTimestamp(event.timesort); | ||||
| 
 | ||||
|                     // Already calculated on 4.0 onwards but this will be live.
 | ||||
|                     event.overdue = event.timesort < now; | ||||
| 
 | ||||
|                     if (eventsByDay[dayTimestamp]) { | ||||
|                         eventsByDay[dayTimestamp].push(event); | ||||
|                     } else { | ||||
| @ -89,20 +85,34 @@ export class AddonBlockTimelineEventsComponent implements OnChanges { | ||||
|     /** | ||||
|      * Filter the events by time. | ||||
|      * | ||||
|      * @param start Number of days to start getting events from today. E.g. -1 will get events from yesterday. | ||||
|      * @param end Number of days after the start. | ||||
|      * @return Filtered events. | ||||
|      */ | ||||
|     protected async filterEventsByTime(start: number, end?: number): Promise<AddonBlockTimelineEvent[]> { | ||||
|         start = moment().add(start, 'days').startOf('day').unix(); | ||||
|         end = end !== undefined ? moment().add(end, 'days').startOf('day').unix() : end; | ||||
|     protected async filterEventsByTime(): Promise<AddonBlockTimelineEvent[]> { | ||||
|         const start = AddonBlockTimeline.getDayStart(this.from); | ||||
|         const end = this.to !== undefined | ||||
|             ? AddonBlockTimeline.getDayStart(this.to) | ||||
|             : undefined; | ||||
| 
 | ||||
|         const now = CoreTimeUtils.timestamp(); | ||||
|         const midnight = AddonBlockTimeline.getDayStart(); | ||||
| 
 | ||||
|         return await Promise.all(this.events.filter((event) => { | ||||
|             if (end) { | ||||
|                 return start <= event.timesort && event.timesort < end; | ||||
|             if (start > event.timesort || (end && event.timesort >= end)) { | ||||
|                 return false; | ||||
|             } | ||||
| 
 | ||||
|             return start <= event.timesort; | ||||
|             // Already calculated on 4.0 onwards but this will be live.
 | ||||
|             event.overdue = event.timesort < now; | ||||
| 
 | ||||
|             if (event.eventtype === 'open' || event.eventtype === 'opensubmission') { | ||||
|                 const dayTimestamp = CoreTimeUtils.getMidnightForTimestamp(event.timesort); | ||||
| 
 | ||||
|                 return dayTimestamp > midnight; | ||||
|             } | ||||
| 
 | ||||
|             // When filtering by overdue, we fetch all events due today, in case any have elapsed already and are overdue.
 | ||||
|             // This means if filtering by overdue, some events fetched might not be required (eg if due later today).
 | ||||
|             return (!this.overdue || event.overdue); | ||||
|         }).map(async (event) => { | ||||
|             event.iconUrl = await CoreCourse.getModuleIconSrc(event.icon.component); | ||||
|             event.modulename = event.modulename || event.icon.component; | ||||
| @ -147,7 +157,8 @@ export class AddonBlockTimelineEventsComponent implements OnChanges { | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| type AddonBlockTimelineEvent = AddonCalendarEvent & { | ||||
| type AddonBlockTimelineEvent = Omit<AddonCalendarEvent, 'eventtype'> & { | ||||
|     eventtype: string; | ||||
|     iconUrl?: string; | ||||
|     iconTitle?: string; | ||||
| }; | ||||
|  | ||||
| @ -60,12 +60,12 @@ | ||||
| 
 | ||||
|     <core-loading [hideUntil]="timeline.loaded" [hidden]="sort != 'sortbydates'"> | ||||
|         <addon-block-timeline-events [events]="timeline.events" [canLoadMore]="timeline.canLoadMore" (loadMore)="loadMore()" | ||||
|             [from]="dataFrom" [to]="dataTo"></addon-block-timeline-events> | ||||
|             [from]="dataFrom" [to]="dataTo" [overdue]="overdue"></addon-block-timeline-events> | ||||
|     </core-loading> | ||||
|     <core-loading [hideUntil]="timelineCourses.loaded" [hidden]="sort != 'sortbycourses'"> | ||||
|         <ng-container *ngFor="let course of timelineCourses.courses"> | ||||
|             <addon-block-timeline-events [events]="course.events" [canLoadMore]="course.canLoadMore" (loadMore)="loadMore(course)" | ||||
|                 [course]="course" [from]="dataFrom" [to]="dataTo"></addon-block-timeline-events> | ||||
|                 [course]="course" [from]="dataFrom" [to]="dataTo" [overdue]="overdue"></addon-block-timeline-events> | ||||
|         </ng-container> | ||||
|         <core-empty-box *ngIf="timelineCourses.courses.length == 0" image="assets/img/icons/courses.svg" | ||||
|             [message]="'addon.block_timeline.noevents' | translate"></core-empty-box> | ||||
|  | ||||
| @ -58,12 +58,15 @@ export class AddonBlockTimelineComponent extends CoreBlockBaseComponent implemen | ||||
| 
 | ||||
|     dataFrom?: number; | ||||
|     dataTo?: number; | ||||
|     overdue = false; | ||||
| 
 | ||||
|     searchEnabled = false; | ||||
|     searchText = ''; | ||||
| 
 | ||||
|     protected courseIds: number[] = []; | ||||
|     protected courseIdsToInvalidate: number[] = []; | ||||
|     protected fetchContentDefaultError = 'Error getting timeline data.'; | ||||
|     protected gradePeriodAfter = 0; | ||||
|     protected gradePeriodBefore = 0; | ||||
| 
 | ||||
|     constructor() { | ||||
|         super('AddonBlockTimelineComponent'); | ||||
| @ -105,8 +108,8 @@ export class AddonBlockTimelineComponent extends CoreBlockBaseComponent implemen | ||||
|         promises.push(AddonBlockTimeline.invalidateActionEventsByCourses()); | ||||
|         promises.push(CoreCourses.invalidateUserCourses()); | ||||
|         promises.push(CoreCourseOptionsDelegate.clearAndInvalidateCoursesOptions()); | ||||
|         if (this.courseIds.length > 0) { | ||||
|             promises.push(CoreCourses.invalidateCoursesByField('ids', this.courseIds.join(','))); | ||||
|         if (this.courseIdsToInvalidate.length > 0) { | ||||
|             promises.push(CoreCourses.invalidateCoursesByField('ids', this.courseIdsToInvalidate.join(','))); | ||||
|         } | ||||
| 
 | ||||
|         return CoreUtils.allPromises(promises); | ||||
| @ -171,13 +174,26 @@ export class AddonBlockTimelineComponent extends CoreBlockBaseComponent implemen | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     protected async fetchMyOverviewTimelineByCourses(): Promise<void> { | ||||
|         try { | ||||
|             this.gradePeriodAfter = parseInt(await this.currentSite.getConfig('coursegraceperiodafter'), 10); | ||||
|             this.gradePeriodBefore = parseInt(await this.currentSite.getConfig('coursegraceperiodbefore'), 10); | ||||
|         } catch { | ||||
|             this.gradePeriodAfter = 0; | ||||
|             this.gradePeriodBefore = 0; | ||||
|         } | ||||
| 
 | ||||
|         // Do not filter courses by date because they can contain activities due.
 | ||||
|         this.timelineCourses.courses = await CoreCoursesHelper.getUserCoursesWithOptions(); | ||||
|         this.courseIdsToInvalidate = this.timelineCourses.courses.map((course) => course.id); | ||||
| 
 | ||||
|         // Filter only in progress courses.
 | ||||
|         this.timelineCourses.courses = this.timelineCourses.courses.filter((course) => | ||||
|             !course.hidden && | ||||
|             !CoreCoursesHelper.isPastCourse(course, this.gradePeriodAfter) && | ||||
|             !CoreCoursesHelper.isFutureCourse(course, this.gradePeriodAfter, this.gradePeriodBefore)); | ||||
| 
 | ||||
|         if (this.timelineCourses.courses.length > 0) { | ||||
|             this.courseIds = this.timelineCourses.courses.map((course) => course.id); | ||||
| 
 | ||||
|             const courseEvents = await AddonBlockTimeline.getActionEventsByCourses(this.courseIds, this.searchText); | ||||
|             const courseEvents = await AddonBlockTimeline.getActionEventsByCourses(this.courseIdsToInvalidate, this.searchText); | ||||
| 
 | ||||
|             this.timelineCourses.courses = this.timelineCourses.courses.filter((course) => { | ||||
|                 if (courseEvents[course.id].events.length == 0) { | ||||
| @ -200,6 +216,7 @@ export class AddonBlockTimelineComponent extends CoreBlockBaseComponent implemen | ||||
|     switchFilter(filter: string): void { | ||||
|         this.filter = filter; | ||||
|         this.currentSite.setLocalSiteConfig('AddonBlockTimelineFilter', this.filter); | ||||
|         this.overdue = this.filter === 'overdue'; | ||||
| 
 | ||||
|         switch (this.filter) { | ||||
|             case 'overdue': | ||||
|  | ||||
| @ -55,7 +55,7 @@ export class AddonBlockTimelineProvider { | ||||
|     ): Promise<{ events: AddonCalendarEvent[]; canLoadMore?: number }> { | ||||
|         const site = await CoreSites.getSite(siteId); | ||||
| 
 | ||||
|         const time = moment().subtract(14, 'days').unix(); // Check two weeks ago.
 | ||||
|         const time = this.getDayStart(-14); // Check two weeks ago.
 | ||||
| 
 | ||||
|         const data: AddonCalendarGetActionEventsByCourseWSParams = { | ||||
|             timesortfrom: time, | ||||
| @ -109,7 +109,7 @@ export class AddonBlockTimelineProvider { | ||||
|     ): Promise<{[courseId: string]: { events: AddonCalendarEvent[]; canLoadMore?: number } }> { | ||||
|         const site = await CoreSites.getSite(siteId); | ||||
| 
 | ||||
|         const time = moment().subtract(14, 'days').unix(); // Check two weeks ago.
 | ||||
|         const time = this.getDayStart(-14); // Check two weeks ago.
 | ||||
| 
 | ||||
|         const data: AddonCalendarGetActionEventsByCoursesWSParams = { | ||||
|             timesortfrom: time, | ||||
| @ -164,7 +164,7 @@ export class AddonBlockTimelineProvider { | ||||
|     ): Promise<{ events: AddonCalendarEvent[]; canLoadMore?: number }> { | ||||
|         const site = await CoreSites.getSite(siteId); | ||||
| 
 | ||||
|         const timesortfrom = moment().subtract(14, 'days').unix(); // Check two weeks ago.
 | ||||
|         const timesortfrom = this.getDayStart(-14); // Check two weeks ago.
 | ||||
|         const limitnum = AddonBlockTimelineProvider.EVENTS_LIMIT; | ||||
| 
 | ||||
|         const data: AddonCalendarGetActionEventsByTimesortWSParams = { | ||||
| @ -275,6 +275,16 @@ export class AddonBlockTimelineProvider { | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns the timestamp at the start of the day with an optional offset. | ||||
|      * | ||||
|      * @param daysOffset Offset days to add or substract. | ||||
|      * @return timestamp. | ||||
|      */ | ||||
|     getDayStart(daysOffset = 0): number { | ||||
|         return moment().startOf('day').add(daysOffset, 'days').unix(); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| export const AddonBlockTimeline = makeSingleton(AddonBlockTimelineProvider); | ||||
|  | ||||
| @ -45,7 +45,7 @@ import { CoreText } from '@singletons/text'; | ||||
| const ROOT_CACHE_KEY = 'mmaCalendar:'; | ||||
| 
 | ||||
| /** | ||||
|  * Context levels enumeration. | ||||
|  * Main calendar Event types enumeration. | ||||
|  */ | ||||
| export enum AddonCalendarEventType { | ||||
|     SITE = 'site', | ||||
|  | ||||
| @ -25,6 +25,7 @@ import { | ||||
| import { makeSingleton, Translate } from '@singletons'; | ||||
| import { CoreWSExternalFile } from '@services/ws'; | ||||
| import { AddonCourseCompletion } from '@addons/coursecompletion/services/coursecompletion'; | ||||
| import moment from 'moment'; | ||||
| 
 | ||||
| /** | ||||
|  * Helper to gather some common courses functions. | ||||
| @ -293,6 +294,51 @@ export class CoreCoursesHelperProvider { | ||||
|         })); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Calculates if course date is past. | ||||
|      * | ||||
|      * @param course Course Object. | ||||
|      * @param gradePeriodAfter Classify past courses as in progress for these many days after the course end date. | ||||
|      * @return Wether the course is past. | ||||
|      */ | ||||
|     isPastCourse(course: CoreEnrolledCourseDataWithOptions, gradePeriodAfter = 0): boolean { | ||||
|         if (course.completed) { | ||||
|             return true; | ||||
|         } | ||||
| 
 | ||||
|         if (!course.enddate) { | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         // Calculate the end date to use for display classification purposes, incorporating the grace period, if any.
 | ||||
|         const endDate = moment(course.enddate * 1000).add(gradePeriodAfter, 'days').valueOf(); | ||||
| 
 | ||||
|         return endDate < Date.now(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Calculates if course date is future. | ||||
|      * | ||||
|      * @param course Course Object. | ||||
|      * @param gradePeriodAfter Classify past courses as in progress for these many days after the course end date. | ||||
|      * @param gradePeriodBefore Classify future courses as in progress for these many days prior to the course start date. | ||||
|      * @return Wether the course is future. | ||||
|      */ | ||||
|     isFutureCourse( | ||||
|         course: CoreEnrolledCourseDataWithOptions, | ||||
|         gradePeriodAfter = 0, | ||||
|         gradePeriodBefore = 0, | ||||
|     ): boolean { | ||||
|         if (this.isPastCourse(course, gradePeriodAfter) || !course.startdate) { | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         // Calculate the start date to use for display classification purposes, incorporating the grace period, if any.
 | ||||
|         const startDate = moment(course.startdate * 1000).subtract(gradePeriodBefore, 'days').valueOf(); | ||||
| 
 | ||||
|         return startDate > Date.now(); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| export const CoreCoursesHelper = makeSingleton(CoreCoursesHelperProvider); | ||||
|  | ||||
| @ -81,7 +81,7 @@ export class CoreTime { | ||||
|     /** | ||||
|      * Converts a number of seconds into a short human readable format: minutes and seconds, in fromat: 3' 27''. | ||||
|      * | ||||
|      * @param seconds Seconds | ||||
|      * @param duration Duration in seconds. | ||||
|      * @return Short human readable text. | ||||
|      */ | ||||
|     static formatTimeShort(duration: number): string { | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user