Skip to content

Commit f0c544d

Browse files
authored
Merge pull request #3268 from crazyserver/MOBILE-3881
Mobile 3881
2 parents a1f6f91 + 0f5416e commit f0c544d

File tree

8 files changed

+123
-72
lines changed

8 files changed

+123
-72
lines changed

src/addons/block/myoverview/components/myoverview/myoverview.ts

Lines changed: 9 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ import { CoreDomUtils } from '@services/utils/dom';
2727
import { CoreTextUtils } from '@services/utils/text';
2828
import { AddonCourseCompletion } from '@addons/coursecompletion/services/coursecompletion';
2929
import { IonSearchbar } from '@ionic/angular';
30-
import moment from 'moment';
3130
import { CoreNavigator } from '@services/navigator';
3231

3332
const FILTER_PRIORITY: AddonBlockMyOverviewTimeFilters[] =
@@ -478,13 +477,19 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem
478477
break;
479478
case 'inprogress':
480479
this.filteredCourses = this.filteredCourses.filter((course) =>
481-
!course.hidden && !this.isPastCourse(course) && !this.isFutureCourse(course));
480+
!course.hidden &&
481+
!CoreCoursesHelper.isPastCourse(course, this.gradePeriodAfter) &&
482+
!CoreCoursesHelper.isFutureCourse(course, this.gradePeriodAfter, this.gradePeriodBefore));
482483
break;
483484
case 'future':
484-
this.filteredCourses = this.filteredCourses.filter((course) => !course.hidden && this.isFutureCourse(course));
485+
this.filteredCourses = this.filteredCourses.filter((course) =>
486+
!course.hidden &&
487+
CoreCoursesHelper.isFutureCourse(course, this.gradePeriodAfter, this.gradePeriodBefore));
485488
break;
486489
case 'past':
487-
this.filteredCourses = this.filteredCourses.filter((course) => !course.hidden && this.isPastCourse(course));
490+
this.filteredCourses = this.filteredCourses.filter((course) =>
491+
!course.hidden &&
492+
CoreCoursesHelper.isPastCourse(course, this.gradePeriodAfter));
488493
break;
489494
case 'favourite':
490495
this.filteredCourses = this.filteredCourses.filter((course) => !course.hidden && course.isfavourite);
@@ -515,44 +520,6 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem
515520
this.initPrefetchCoursesIcons();
516521
}
517522

518-
/**
519-
* Calculates if course date is past.
520-
*
521-
* @param course Course Object.
522-
* @return Wether the course is past.
523-
*/
524-
protected isPastCourse(course: CoreEnrolledCourseDataWithOptions): boolean {
525-
if (course.completed) {
526-
return true;
527-
}
528-
529-
if (!course.enddate) {
530-
return false;
531-
}
532-
533-
// Calculate the end date to use for display classification purposes, incorporating the grace period, if any.
534-
const endDate = moment(course.enddate * 1000).add(this.gradePeriodAfter, 'days').valueOf();
535-
536-
return endDate < this.today;
537-
}
538-
539-
/**
540-
* Calculates if course date is future.
541-
*
542-
* @param course Course Object.
543-
* @return Wether the course is future.
544-
*/
545-
protected isFutureCourse(course: CoreEnrolledCourseDataWithOptions): boolean {
546-
if (this.isPastCourse(course) || !course.startdate) {
547-
return false;
548-
}
549-
550-
// Calculate the start date to use for display classification purposes, incorporating the grace period, if any.
551-
const startDate = moment(course.startdate * 1000).subtract(this.gradePeriodBefore, 'days').valueOf();
552-
553-
return startDate > this.today;
554-
}
555-
556523
/**
557524
* Sort courses
558525
*

src/addons/block/timeline/components/events/events.ts

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@ import { CoreDomUtils } from '@services/utils/dom';
1818
import { CoreTextUtils } from '@services/utils/text';
1919
import { CoreTimeUtils } from '@services/utils/time';
2020
import { CoreCourse } from '@features/course/services/course';
21-
import moment from 'moment';
2221
import { CoreContentLinksHelper } from '@features/contentlinks/services/contentlinks-helper';
2322
import { AddonCalendarEvent } from '@addons/calendar/services/calendar';
2423
import { CoreEnrolledCourseDataWithOptions } from '@features/courses/services/courses-helper';
24+
import { AddonBlockTimeline } from '../../services/timeline';
2525

2626
/**
2727
* Directive to render a list of events in course overview.
@@ -37,6 +37,7 @@ export class AddonBlockTimelineEventsComponent implements OnChanges {
3737
@Input() course?: CoreEnrolledCourseDataWithOptions; // Whether to show the course name.
3838
@Input() from = 0; // Number of days from today to offset the events.
3939
@Input() to?: number; // Number of days from today to limit the events to. If not defined, no limit.
40+
@Input() overdue = false; // If filtering overdue events or not.
4041
@Input() canLoadMore = false; // Whether more events can be loaded.
4142
@Output() loadMore = new EventEmitter(); // Notify that more events should be loaded.
4243

@@ -53,18 +54,13 @@ export class AddonBlockTimelineEventsComponent implements OnChanges {
5354

5455
if (changes.events || changes.from || changes.to) {
5556
if (this.events) {
56-
const filteredEvents = await this.filterEventsByTime(this.from, this.to);
57+
const filteredEvents = await this.filterEventsByTime();
5758
this.empty = !filteredEvents || filteredEvents.length <= 0;
5859

59-
const now = CoreTimeUtils.timestamp();
60-
61-
const eventsByDay: Record<number, AddonCalendarEvent[]> = {};
60+
const eventsByDay: Record<number, AddonBlockTimelineEvent[]> = {};
6261
filteredEvents.forEach((event) => {
6362
const dayTimestamp = CoreTimeUtils.getMidnightForTimestamp(event.timesort);
6463

65-
// Already calculated on 4.0 onwards but this will be live.
66-
event.overdue = event.timesort < now;
67-
6864
if (eventsByDay[dayTimestamp]) {
6965
eventsByDay[dayTimestamp].push(event);
7066
} else {
@@ -89,20 +85,34 @@ export class AddonBlockTimelineEventsComponent implements OnChanges {
8985
/**
9086
* Filter the events by time.
9187
*
92-
* @param start Number of days to start getting events from today. E.g. -1 will get events from yesterday.
93-
* @param end Number of days after the start.
9488
* @return Filtered events.
9589
*/
96-
protected async filterEventsByTime(start: number, end?: number): Promise<AddonBlockTimelineEvent[]> {
97-
start = moment().add(start, 'days').startOf('day').unix();
98-
end = end !== undefined ? moment().add(end, 'days').startOf('day').unix() : end;
90+
protected async filterEventsByTime(): Promise<AddonBlockTimelineEvent[]> {
91+
const start = AddonBlockTimeline.getDayStart(this.from);
92+
const end = this.to !== undefined
93+
? AddonBlockTimeline.getDayStart(this.to)
94+
: undefined;
95+
96+
const now = CoreTimeUtils.timestamp();
97+
const midnight = AddonBlockTimeline.getDayStart();
9998

10099
return await Promise.all(this.events.filter((event) => {
101-
if (end) {
102-
return start <= event.timesort && event.timesort < end;
100+
if (start > event.timesort || (end && event.timesort >= end)) {
101+
return false;
102+
}
103+
104+
// Already calculated on 4.0 onwards but this will be live.
105+
event.overdue = event.timesort < now;
106+
107+
if (event.eventtype === 'open' || event.eventtype === 'opensubmission') {
108+
const dayTimestamp = CoreTimeUtils.getMidnightForTimestamp(event.timesort);
109+
110+
return dayTimestamp > midnight;
103111
}
104112

105-
return start <= event.timesort;
113+
// When filtering by overdue, we fetch all events due today, in case any have elapsed already and are overdue.
114+
// This means if filtering by overdue, some events fetched might not be required (eg if due later today).
115+
return (!this.overdue || event.overdue);
106116
}).map(async (event) => {
107117
event.iconUrl = await CoreCourse.getModuleIconSrc(event.icon.component);
108118
event.modulename = event.modulename || event.icon.component;
@@ -147,7 +157,8 @@ export class AddonBlockTimelineEventsComponent implements OnChanges {
147157

148158
}
149159

150-
type AddonBlockTimelineEvent = AddonCalendarEvent & {
160+
type AddonBlockTimelineEvent = Omit<AddonCalendarEvent, 'eventtype'> & {
161+
eventtype: string;
151162
iconUrl?: string;
152163
iconTitle?: string;
153164
};

src/addons/block/timeline/components/timeline/addon-block-timeline.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,12 +60,12 @@ <h2>{{ 'addon.block_timeline.pluginname' | translate }}</h2>
6060

6161
<core-loading [hideUntil]="timeline.loaded" [hidden]="sort != 'sortbydates'">
6262
<addon-block-timeline-events [events]="timeline.events" [canLoadMore]="timeline.canLoadMore" (loadMore)="loadMore()"
63-
[from]="dataFrom" [to]="dataTo"></addon-block-timeline-events>
63+
[from]="dataFrom" [to]="dataTo" [overdue]="overdue"></addon-block-timeline-events>
6464
</core-loading>
6565
<core-loading [hideUntil]="timelineCourses.loaded" [hidden]="sort != 'sortbycourses'">
6666
<ng-container *ngFor="let course of timelineCourses.courses">
6767
<addon-block-timeline-events [events]="course.events" [canLoadMore]="course.canLoadMore" (loadMore)="loadMore(course)"
68-
[course]="course" [from]="dataFrom" [to]="dataTo"></addon-block-timeline-events>
68+
[course]="course" [from]="dataFrom" [to]="dataTo" [overdue]="overdue"></addon-block-timeline-events>
6969
</ng-container>
7070
<core-empty-box *ngIf="timelineCourses.courses.length == 0" image="assets/img/icons/courses.svg"
7171
[message]="'addon.block_timeline.noevents' | translate"></core-empty-box>

src/addons/block/timeline/components/timeline/timeline.ts

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,15 @@ export class AddonBlockTimelineComponent extends CoreBlockBaseComponent implemen
5858

5959
dataFrom?: number;
6060
dataTo?: number;
61+
overdue = false;
6162

6263
searchEnabled = false;
6364
searchText = '';
6465

65-
protected courseIds: number[] = [];
66+
protected courseIdsToInvalidate: number[] = [];
6667
protected fetchContentDefaultError = 'Error getting timeline data.';
68+
protected gradePeriodAfter = 0;
69+
protected gradePeriodBefore = 0;
6770

6871
constructor() {
6972
super('AddonBlockTimelineComponent');
@@ -105,8 +108,8 @@ export class AddonBlockTimelineComponent extends CoreBlockBaseComponent implemen
105108
promises.push(AddonBlockTimeline.invalidateActionEventsByCourses());
106109
promises.push(CoreCourses.invalidateUserCourses());
107110
promises.push(CoreCourseOptionsDelegate.clearAndInvalidateCoursesOptions());
108-
if (this.courseIds.length > 0) {
109-
promises.push(CoreCourses.invalidateCoursesByField('ids', this.courseIds.join(',')));
111+
if (this.courseIdsToInvalidate.length > 0) {
112+
promises.push(CoreCourses.invalidateCoursesByField('ids', this.courseIdsToInvalidate.join(',')));
110113
}
111114

112115
return CoreUtils.allPromises(promises);
@@ -171,13 +174,26 @@ export class AddonBlockTimelineComponent extends CoreBlockBaseComponent implemen
171174
* @return Promise resolved when done.
172175
*/
173176
protected async fetchMyOverviewTimelineByCourses(): Promise<void> {
177+
try {
178+
this.gradePeriodAfter = parseInt(await this.currentSite.getConfig('coursegraceperiodafter'), 10);
179+
this.gradePeriodBefore = parseInt(await this.currentSite.getConfig('coursegraceperiodbefore'), 10);
180+
} catch {
181+
this.gradePeriodAfter = 0;
182+
this.gradePeriodBefore = 0;
183+
}
184+
174185
// Do not filter courses by date because they can contain activities due.
175186
this.timelineCourses.courses = await CoreCoursesHelper.getUserCoursesWithOptions();
187+
this.courseIdsToInvalidate = this.timelineCourses.courses.map((course) => course.id);
176188

177-
if (this.timelineCourses.courses.length > 0) {
178-
this.courseIds = this.timelineCourses.courses.map((course) => course.id);
189+
// Filter only in progress courses.
190+
this.timelineCourses.courses = this.timelineCourses.courses.filter((course) =>
191+
!course.hidden &&
192+
!CoreCoursesHelper.isPastCourse(course, this.gradePeriodAfter) &&
193+
!CoreCoursesHelper.isFutureCourse(course, this.gradePeriodAfter, this.gradePeriodBefore));
179194

180-
const courseEvents = await AddonBlockTimeline.getActionEventsByCourses(this.courseIds, this.searchText);
195+
if (this.timelineCourses.courses.length > 0) {
196+
const courseEvents = await AddonBlockTimeline.getActionEventsByCourses(this.courseIdsToInvalidate, this.searchText);
181197

182198
this.timelineCourses.courses = this.timelineCourses.courses.filter((course) => {
183199
if (courseEvents[course.id].events.length == 0) {
@@ -200,6 +216,7 @@ export class AddonBlockTimelineComponent extends CoreBlockBaseComponent implemen
200216
switchFilter(filter: string): void {
201217
this.filter = filter;
202218
this.currentSite.setLocalSiteConfig('AddonBlockTimelineFilter', this.filter);
219+
this.overdue = this.filter === 'overdue';
203220

204221
switch (this.filter) {
205222
case 'overdue':

src/addons/block/timeline/services/timeline.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ export class AddonBlockTimelineProvider {
5555
): Promise<{ events: AddonCalendarEvent[]; canLoadMore?: number }> {
5656
const site = await CoreSites.getSite(siteId);
5757

58-
const time = moment().subtract(14, 'days').unix(); // Check two weeks ago.
58+
const time = this.getDayStart(-14); // Check two weeks ago.
5959

6060
const data: AddonCalendarGetActionEventsByCourseWSParams = {
6161
timesortfrom: time,
@@ -109,7 +109,7 @@ export class AddonBlockTimelineProvider {
109109
): Promise<{[courseId: string]: { events: AddonCalendarEvent[]; canLoadMore?: number } }> {
110110
const site = await CoreSites.getSite(siteId);
111111

112-
const time = moment().subtract(14, 'days').unix(); // Check two weeks ago.
112+
const time = this.getDayStart(-14); // Check two weeks ago.
113113

114114
const data: AddonCalendarGetActionEventsByCoursesWSParams = {
115115
timesortfrom: time,
@@ -164,7 +164,7 @@ export class AddonBlockTimelineProvider {
164164
): Promise<{ events: AddonCalendarEvent[]; canLoadMore?: number }> {
165165
const site = await CoreSites.getSite(siteId);
166166

167-
const timesortfrom = moment().subtract(14, 'days').unix(); // Check two weeks ago.
167+
const timesortfrom = this.getDayStart(-14); // Check two weeks ago.
168168
const limitnum = AddonBlockTimelineProvider.EVENTS_LIMIT;
169169

170170
const data: AddonCalendarGetActionEventsByTimesortWSParams = {
@@ -275,6 +275,16 @@ export class AddonBlockTimelineProvider {
275275
};
276276
}
277277

278+
/**
279+
* Returns the timestamp at the start of the day with an optional offset.
280+
*
281+
* @param daysOffset Offset days to add or substract.
282+
* @return timestamp.
283+
*/
284+
getDayStart(daysOffset = 0): number {
285+
return moment().startOf('day').add(daysOffset, 'days').unix();
286+
}
287+
278288
}
279289

280290
export const AddonBlockTimeline = makeSingleton(AddonBlockTimelineProvider);

src/addons/calendar/services/calendar.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ import { CoreText } from '@singletons/text';
4545
const ROOT_CACHE_KEY = 'mmaCalendar:';
4646

4747
/**
48-
* Context levels enumeration.
48+
* Main calendar Event types enumeration.
4949
*/
5050
export enum AddonCalendarEventType {
5151
SITE = 'site',

src/core/features/courses/services/courses-helper.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
import { makeSingleton, Translate } from '@singletons';
2626
import { CoreWSExternalFile } from '@services/ws';
2727
import { AddonCourseCompletion } from '@addons/coursecompletion/services/coursecompletion';
28+
import moment from 'moment';
2829

2930
/**
3031
* Helper to gather some common courses functions.
@@ -293,6 +294,51 @@ export class CoreCoursesHelperProvider {
293294
}));
294295
}
295296

297+
/**
298+
* Calculates if course date is past.
299+
*
300+
* @param course Course Object.
301+
* @param gradePeriodAfter Classify past courses as in progress for these many days after the course end date.
302+
* @return Wether the course is past.
303+
*/
304+
isPastCourse(course: CoreEnrolledCourseDataWithOptions, gradePeriodAfter = 0): boolean {
305+
if (course.completed) {
306+
return true;
307+
}
308+
309+
if (!course.enddate) {
310+
return false;
311+
}
312+
313+
// Calculate the end date to use for display classification purposes, incorporating the grace period, if any.
314+
const endDate = moment(course.enddate * 1000).add(gradePeriodAfter, 'days').valueOf();
315+
316+
return endDate < Date.now();
317+
}
318+
319+
/**
320+
* Calculates if course date is future.
321+
*
322+
* @param course Course Object.
323+
* @param gradePeriodAfter Classify past courses as in progress for these many days after the course end date.
324+
* @param gradePeriodBefore Classify future courses as in progress for these many days prior to the course start date.
325+
* @return Wether the course is future.
326+
*/
327+
isFutureCourse(
328+
course: CoreEnrolledCourseDataWithOptions,
329+
gradePeriodAfter = 0,
330+
gradePeriodBefore = 0,
331+
): boolean {
332+
if (this.isPastCourse(course, gradePeriodAfter) || !course.startdate) {
333+
return false;
334+
}
335+
336+
// Calculate the start date to use for display classification purposes, incorporating the grace period, if any.
337+
const startDate = moment(course.startdate * 1000).subtract(gradePeriodBefore, 'days').valueOf();
338+
339+
return startDate > Date.now();
340+
}
341+
296342
}
297343

298344
export const CoreCoursesHelper = makeSingleton(CoreCoursesHelperProvider);

src/core/singletons/time.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ export class CoreTime {
8181
/**
8282
* Converts a number of seconds into a short human readable format: minutes and seconds, in fromat: 3' 27''.
8383
*
84-
* @param seconds Seconds
84+
* @param duration Duration in seconds.
8585
* @return Short human readable text.
8686
*/
8787
static formatTimeShort(duration: number): string {

0 commit comments

Comments
 (0)