MOBILE-3927 calendar: Add swipe to calendar daily view
parent
741880f8df
commit
d527695977
|
@ -43,7 +43,10 @@ import { CoreCategoryData, CoreCourses } from '@features/courses/services/course
|
||||||
import { CoreApp } from '@services/app';
|
import { CoreApp } from '@services/app';
|
||||||
import { CoreLocalNotifications } from '@services/local-notifications';
|
import { CoreLocalNotifications } from '@services/local-notifications';
|
||||||
import { CoreSwipeCurrentItemData, CoreSwipeSlidesComponent } from '@components/swipe-slides/swipe-slides';
|
import { CoreSwipeCurrentItemData, CoreSwipeSlidesComponent } from '@components/swipe-slides/swipe-slides';
|
||||||
import { CoreSwipeSlidesDynamicItemsManagerSource } from '@classes/items-management/slides-dynamic-items-manager-source';
|
import {
|
||||||
|
CoreSwipeSlidesDynamicItem,
|
||||||
|
CoreSwipeSlidesDynamicItemsManagerSource,
|
||||||
|
} from '@classes/items-management/slides-dynamic-items-manager-source';
|
||||||
import { CoreSwipeSlidesDynamicItemsManager } from '@classes/items-management/slides-dynamic-items-manager';
|
import { CoreSwipeSlidesDynamicItemsManager } from '@classes/items-management/slides-dynamic-items-manager';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -113,8 +116,8 @@ export class AddonCalendarCalendarComponent implements OnInit, DoCheck, OnDestro
|
||||||
this.undeleteEvent(data.eventId);
|
this.undeleteEvent(data.eventId);
|
||||||
|
|
||||||
// Remove it from the list of deleted events if it's there.
|
// Remove it from the list of deleted events if it's there.
|
||||||
const index = this.manager?.getSource().deletedEvents.indexOf(data.eventId);
|
const index = this.manager?.getSource().deletedEvents.indexOf(data.eventId) ?? -1;
|
||||||
if (index !== undefined && index != -1) {
|
if (index != -1) {
|
||||||
this.manager?.getSource().deletedEvents.splice(index, 1);
|
this.manager?.getSource().deletedEvents.splice(index, 1);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -128,15 +131,15 @@ export class AddonCalendarCalendarComponent implements OnInit, DoCheck, OnDestro
|
||||||
* Component loaded.
|
* Component loaded.
|
||||||
*/
|
*/
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
const now = new Date();
|
const currentMonth = CoreTimeUtils.getCurrentMonth();
|
||||||
|
|
||||||
this.canNavigate = typeof this.canNavigate == 'undefined' ? true : CoreUtils.isTrueOrOne(this.canNavigate);
|
this.canNavigate = typeof this.canNavigate == 'undefined' ? true : CoreUtils.isTrueOrOne(this.canNavigate);
|
||||||
this.displayNavButtons = typeof this.displayNavButtons == 'undefined' ? true :
|
this.displayNavButtons = typeof this.displayNavButtons == 'undefined' ? true :
|
||||||
CoreUtils.isTrueOrOne(this.displayNavButtons);
|
CoreUtils.isTrueOrOne(this.displayNavButtons);
|
||||||
|
|
||||||
const source = new AddonCalendarMonthSlidesItemsManagerSource(this, {
|
const source = new AddonCalendarMonthSlidesItemsManagerSource(this, {
|
||||||
year: this.initialYear ?? now.getFullYear(),
|
year: this.initialYear ?? currentMonth.year,
|
||||||
monthNumber: this.initialMonth ?? now.getMonth() + 1,
|
monthNumber: this.initialMonth ?? currentMonth.monthNumber,
|
||||||
});
|
});
|
||||||
this.manager = new CoreSwipeSlidesDynamicItemsManager(source);
|
this.manager = new CoreSwipeSlidesDynamicItemsManager(source);
|
||||||
|
|
||||||
|
@ -157,7 +160,7 @@ export class AddonCalendarCalendarComponent implements OnInit, DoCheck, OnDestro
|
||||||
if (changes) {
|
if (changes) {
|
||||||
items.forEach((month) => {
|
items.forEach((month) => {
|
||||||
if (month.loaded) {
|
if (month.loaded) {
|
||||||
this.filterEvents(month.weeks);
|
this.manager?.getSource().filterEvents(month.weeks, this.filter);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -177,7 +180,7 @@ export class AddonCalendarCalendarComponent implements OnInit, DoCheck, OnDestro
|
||||||
try {
|
try {
|
||||||
await this.manager?.getSource().fetchData();
|
await this.manager?.getSource().fetchData();
|
||||||
|
|
||||||
await this.manager?.getSource().load();
|
await this.manager?.getSource().load(this.manager?.getSelectedItem());
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
CoreDomUtils.showErrorModalDefault(error, 'addon.calendar.errorloadevents', true);
|
CoreDomUtils.showErrorModalDefault(error, 'addon.calendar.errorloadevents', true);
|
||||||
}
|
}
|
||||||
|
@ -186,11 +189,9 @@ export class AddonCalendarCalendarComponent implements OnInit, DoCheck, OnDestro
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load current month and preload next and previous ones.
|
* Update data related to month being viewed.
|
||||||
*
|
|
||||||
* @return Promise resolved when done.
|
|
||||||
*/
|
*/
|
||||||
async viewMonth(month: YearAndMonth): Promise<void> {
|
viewMonth(month: YearAndMonth): void {
|
||||||
// Calculate the period name. We don't use the one in result because it's in server's language.
|
// Calculate the period name. We don't use the one in result because it's in server's language.
|
||||||
this.periodName = CoreTimeUtils.userDate(
|
this.periodName = CoreTimeUtils.userDate(
|
||||||
new Date(month.year, month.monthNumber - 1).getTime(),
|
new Date(month.year, month.monthNumber - 1).getTime(),
|
||||||
|
@ -199,35 +200,19 @@ export class AddonCalendarCalendarComponent implements OnInit, DoCheck, OnDestro
|
||||||
this.calculateIsCurrentMonth();
|
this.calculateIsCurrentMonth();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Filter events based on the filter popover.
|
|
||||||
*
|
|
||||||
* @param weeks Weeks with the events to filter.
|
|
||||||
*/
|
|
||||||
filterEvents(weeks: AddonCalendarWeek[]): void {
|
|
||||||
weeks.forEach((week) => {
|
|
||||||
week.days.forEach((day) => {
|
|
||||||
day.filteredEvents = AddonCalendarHelper.getFilteredEvents(
|
|
||||||
day.eventsFormated || [],
|
|
||||||
this.filter,
|
|
||||||
this.manager?.getSource().categories || {},
|
|
||||||
);
|
|
||||||
|
|
||||||
// Re-calculate some properties.
|
|
||||||
AddonCalendarHelper.calculateDayData(day, day.filteredEvents);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Refresh events.
|
* Refresh events.
|
||||||
*
|
*
|
||||||
* @param afterChange Whether the refresh is done after an event has changed or has been synced.
|
* @param afterChange Whether the refresh is done after an event has changed or has been synced.
|
||||||
* @return Promise resolved when done.
|
* @return Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
async refreshData(): Promise<void> {
|
async refreshData(afterChange = false): Promise<void> {
|
||||||
const visibleMonth = this.slides?.getCurrentItem() || null;
|
const visibleMonth = this.slides?.getCurrentItem() || null;
|
||||||
|
|
||||||
|
if (afterChange) {
|
||||||
|
this.manager?.getSource().markAllItemsDirty();
|
||||||
|
}
|
||||||
|
|
||||||
await this.manager?.getSource().invalidateContent(visibleMonth);
|
await this.manager?.getSource().invalidateContent(visibleMonth);
|
||||||
|
|
||||||
await this.fetchData();
|
await this.fetchData();
|
||||||
|
@ -282,8 +267,8 @@ export class AddonCalendarCalendarComponent implements OnInit, DoCheck, OnDestro
|
||||||
}
|
}
|
||||||
|
|
||||||
this.currentTime = CoreTimeUtils.timestamp();
|
this.currentTime = CoreTimeUtils.timestamp();
|
||||||
this.isCurrentMonth = CoreTimeUtils.isCurrentMonth(visibleMonth);
|
this.isCurrentMonth = CoreTimeUtils.isSameMonth(visibleMonth, CoreTimeUtils.getCurrentMonth());
|
||||||
this.isPastMonth = CoreTimeUtils.isCurrentMonth(visibleMonth);
|
this.isPastMonth = CoreTimeUtils.isPastMonth(visibleMonth);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -305,20 +290,22 @@ export class AddonCalendarCalendarComponent implements OnInit, DoCheck, OnDestro
|
||||||
* @param eventId Event ID.
|
* @param eventId Event ID.
|
||||||
*/
|
*/
|
||||||
protected async undeleteEvent(eventId: number): Promise<void> {
|
protected async undeleteEvent(eventId: number): Promise<void> {
|
||||||
this.manager?.getSource().getItems()?.forEach((month) => {
|
this.manager?.getSource().getItems()?.some((month) => {
|
||||||
if (!month.loaded) {
|
if (!month.loaded) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
month.weeks.forEach((week) => {
|
return month.weeks.some((week) => week.days.some((day) => {
|
||||||
week.days.forEach((day) => {
|
|
||||||
const event = day.eventsFormated?.find((event) => event.id == eventId);
|
const event = day.eventsFormated?.find((event) => event.id == eventId);
|
||||||
|
|
||||||
if (event) {
|
if (event) {
|
||||||
event.deleted = false;
|
event.deleted = false;
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
});
|
|
||||||
});
|
return false;
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -354,17 +341,13 @@ export class AddonCalendarCalendarComponent implements OnInit, DoCheck, OnDestro
|
||||||
/**
|
/**
|
||||||
* Preloaded month.
|
* Preloaded month.
|
||||||
*/
|
*/
|
||||||
type PreloadedMonth = YearAndMonth & {
|
type PreloadedMonth = YearAndMonth & CoreSwipeSlidesDynamicItem & {
|
||||||
loaded: boolean; // Whether the events have been loaded.
|
|
||||||
weekDays: AddonCalendarWeekDaysTranslationKeys[];
|
weekDays: AddonCalendarWeekDaysTranslationKeys[];
|
||||||
weeks: AddonCalendarWeek[];
|
weeks: AddonCalendarWeek[];
|
||||||
needsRefresh?: boolean; // Whether the events needs to be re-loaded.
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// CoreSwipeSlidesDynamicItemsManagerSource
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper to manage swiping within a collection of chapters.
|
* Helper to manage swiping within months.
|
||||||
*/
|
*/
|
||||||
class AddonCalendarMonthSlidesItemsManagerSource extends CoreSwipeSlidesDynamicItemsManagerSource<PreloadedMonth> {
|
class AddonCalendarMonthSlidesItemsManagerSource extends CoreSwipeSlidesDynamicItemsManagerSource<PreloadedMonth> {
|
||||||
|
|
||||||
|
@ -399,6 +382,27 @@ class AddonCalendarMonthSlidesItemsManagerSource extends CoreSwipeSlidesDynamicI
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter events based on the filter popover.
|
||||||
|
*
|
||||||
|
* @param weeks Weeks with the events to filter.
|
||||||
|
* @param filter Filter to apply.
|
||||||
|
*/
|
||||||
|
filterEvents(weeks: AddonCalendarWeek[], filter?: AddonCalendarFilter): void {
|
||||||
|
weeks.forEach((week) => {
|
||||||
|
week.days.forEach((day) => {
|
||||||
|
day.filteredEvents = AddonCalendarHelper.getFilteredEvents(
|
||||||
|
day.eventsFormated || [],
|
||||||
|
filter,
|
||||||
|
this.categories,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Re-calculate some properties.
|
||||||
|
AddonCalendarHelper.calculateDayData(day, day.filteredEvents);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load categories to be able to filter events.
|
* Load categories to be able to filter events.
|
||||||
*
|
*
|
||||||
|
@ -437,8 +441,7 @@ class AddonCalendarMonthSlidesItemsManagerSource extends CoreSwipeSlidesDynamicI
|
||||||
this.offlineEvents = AddonCalendarHelper.classifyIntoMonths(events);
|
this.offlineEvents = AddonCalendarHelper.classifyIntoMonths(events);
|
||||||
|
|
||||||
// Get the IDs of events edited in offline.
|
// Get the IDs of events edited in offline.
|
||||||
const filtered = events.filter((event) => event.id > 0);
|
this.offlineEditedEventsIds = events.filter((event) => event.id > 0).map((event) => event.id);
|
||||||
this.offlineEditedEventsIds = filtered.map((event) => event.id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -463,7 +466,7 @@ class AddonCalendarMonthSlidesItemsManagerSource extends CoreSwipeSlidesDynamicI
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
getItemId(item: YearAndMonth): string | number {
|
getItemId(item: YearAndMonth): string | number {
|
||||||
return `${item.year}#${item.monthNumber}`;
|
return AddonCalendarHelper.getMonthId(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -484,12 +487,6 @@ class AddonCalendarMonthSlidesItemsManagerSource extends CoreSwipeSlidesDynamicI
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
async loadItemData(month: YearAndMonth, preload = false): Promise<PreloadedMonth | null> {
|
async loadItemData(month: YearAndMonth, preload = false): Promise<PreloadedMonth | null> {
|
||||||
// Check if it's already loaded.
|
|
||||||
const existingMonth = this.getItem(month);
|
|
||||||
if (existingMonth && ((existingMonth.loaded && !existingMonth.needsRefresh) || preload)) {
|
|
||||||
return existingMonth;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!preload) {
|
if (!preload) {
|
||||||
this.monthLoaded = false;
|
this.monthLoaded = false;
|
||||||
}
|
}
|
||||||
|
@ -515,7 +512,7 @@ class AddonCalendarMonthSlidesItemsManagerSource extends CoreSwipeSlidesDynamicI
|
||||||
|
|
||||||
const weekDays = AddonCalendar.getWeekDays(result.daynames[0].dayno);
|
const weekDays = AddonCalendar.getWeekDays(result.daynames[0].dayno);
|
||||||
const weeks = result.weeks as AddonCalendarWeek[];
|
const weeks = result.weeks as AddonCalendarWeek[];
|
||||||
const isCurrentMonth = CoreTimeUtils.isCurrentMonth(month);
|
const isCurrentMonth = CoreTimeUtils.isSameMonth(month, CoreTimeUtils.getCurrentMonth());
|
||||||
const currentDay = new Date().getDate();
|
const currentDay = new Date().getDate();
|
||||||
let isPast = true;
|
let isPast = true;
|
||||||
|
|
||||||
|
@ -552,22 +549,11 @@ class AddonCalendarMonthSlidesItemsManagerSource extends CoreSwipeSlidesDynamicI
|
||||||
// Merge the online events with offline data.
|
// Merge the online events with offline data.
|
||||||
this.mergeEvents(month, weeks);
|
this.mergeEvents(month, weeks);
|
||||||
// Filter events by course.
|
// Filter events by course.
|
||||||
this.calendarComponent.filterEvents(weeks);
|
this.filterEvents(weeks, this.calendarComponent.filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (existingMonth) {
|
|
||||||
// Month already exists, update it.
|
|
||||||
existingMonth.loaded = !preload;
|
|
||||||
existingMonth.weeks = weeks;
|
|
||||||
existingMonth.weekDays = weekDays;
|
|
||||||
|
|
||||||
return existingMonth;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the preloaded month at the right position.
|
|
||||||
return {
|
return {
|
||||||
...month,
|
...month,
|
||||||
loaded: !preload,
|
|
||||||
weeks,
|
weeks,
|
||||||
weekDays,
|
weekDays,
|
||||||
};
|
};
|
||||||
|
@ -586,7 +572,7 @@ class AddonCalendarMonthSlidesItemsManagerSource extends CoreSwipeSlidesDynamicI
|
||||||
*/
|
*/
|
||||||
mergeEvents(month: YearAndMonth, weeks: AddonCalendarWeek[]): void {
|
mergeEvents(month: YearAndMonth, weeks: AddonCalendarWeek[]): void {
|
||||||
const monthOfflineEvents: { [day: number]: AddonCalendarEventToDisplay[] } =
|
const monthOfflineEvents: { [day: number]: AddonCalendarEventToDisplay[] } =
|
||||||
this.offlineEvents[AddonCalendarHelper.getMonthId(month.year, month.monthNumber)];
|
this.offlineEvents[AddonCalendarHelper.getMonthId(month)];
|
||||||
|
|
||||||
weeks.forEach((week) => {
|
weeks.forEach((week) => {
|
||||||
week.days.forEach((day) => {
|
week.days.forEach((day) => {
|
||||||
|
@ -637,7 +623,7 @@ class AddonCalendarMonthSlidesItemsManagerSource extends CoreSwipeSlidesDynamicI
|
||||||
this.categoriesRetrieved = false; // Get categories again.
|
this.categoriesRetrieved = false; // Get categories again.
|
||||||
|
|
||||||
if (visibleMonth) {
|
if (visibleMonth) {
|
||||||
visibleMonth.needsRefresh = true;
|
visibleMonth.dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
await Promise.all(promises);
|
await Promise.all(promises);
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
<core-context-menu-item *ngIf="!isCurrentDay" [priority]="900" [content]="'addon.calendar.today' | translate"
|
<core-context-menu-item *ngIf="!isCurrentDay" [priority]="900" [content]="'addon.calendar.today' | translate"
|
||||||
iconAction="fas-calendar-day" (action)="goToCurrentDay()">
|
iconAction="fas-calendar-day" (action)="goToCurrentDay()">
|
||||||
</core-context-menu-item>
|
</core-context-menu-item>
|
||||||
<core-context-menu-item [hidden]="!loaded || !hasOffline || !isOnline" [priority]="400"
|
<core-context-menu-item [hidden]="!loaded || !this.visibleDayHasOffline() || !isOnline" [priority]="400"
|
||||||
[content]="'core.settings.synchronizenow' | translate" (action)="doRefresh(undefined, $event)" [iconAction]="syncIcon"
|
[content]="'core.settings.synchronizenow' | translate" (action)="doRefresh(undefined, $event)" [iconAction]="syncIcon"
|
||||||
[closeOnClick]="false">
|
[closeOnClick]="false">
|
||||||
</core-context-menu-item>
|
</core-context-menu-item>
|
||||||
|
@ -27,10 +27,11 @@
|
||||||
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||||
</ion-refresher>
|
</ion-refresher>
|
||||||
|
|
||||||
|
<core-loading [hideUntil]="loaded">
|
||||||
<!-- Period name and arrows to navigate. -->
|
<!-- Period name and arrows to navigate. -->
|
||||||
<ion-grid class="ion-no-padding safe-area-padding">
|
<ion-grid class="ion-no-padding safe-area-padding">
|
||||||
<ion-row class="ion-align-items-center">
|
<ion-row class="ion-align-items-center">
|
||||||
<ion-col class="ion-text-start" *ngIf="currentMoment">
|
<ion-col class="ion-text-start">
|
||||||
<ion-button fill="clear" (click)="loadPrevious()" [attr.aria-label]="'addon.calendar.dayprev' | translate">
|
<ion-button fill="clear" (click)="loadPrevious()" [attr.aria-label]="'addon.calendar.dayprev' | translate">
|
||||||
<ion-icon name="fas-chevron-left" slot="icon-only" aria-hidden="true"></ion-icon>
|
<ion-icon name="fas-chevron-left" slot="icon-only" aria-hidden="true"></ion-icon>
|
||||||
</ion-button>
|
</ion-button>
|
||||||
|
@ -38,7 +39,7 @@
|
||||||
<ion-col class="ion-text-center addon-calendar-period">
|
<ion-col class="ion-text-center addon-calendar-period">
|
||||||
<h3>{{ periodName }}</h3>
|
<h3>{{ periodName }}</h3>
|
||||||
</ion-col>
|
</ion-col>
|
||||||
<ion-col class="ion-text-end" *ngIf="currentMoment">
|
<ion-col class="ion-text-end">
|
||||||
<ion-button fill="clear" (click)="loadNext()" [attr.aria-label]="'addon.calendar.daynext' | translate">
|
<ion-button fill="clear" (click)="loadNext()" [attr.aria-label]="'addon.calendar.daynext' | translate">
|
||||||
<ion-icon name="fas-chevron-right" slot="icon-only" aria-hidden="true"></ion-icon>
|
<ion-icon name="fas-chevron-right" slot="icon-only" aria-hidden="true"></ion-icon>
|
||||||
</ion-button>
|
</ion-button>
|
||||||
|
@ -46,27 +47,31 @@
|
||||||
</ion-row>
|
</ion-row>
|
||||||
</ion-grid>
|
</ion-grid>
|
||||||
|
|
||||||
<core-loading [hideUntil]="loaded" class="safe-area-padding">
|
<core-swipe-slides [manager]="manager" (onWillChange)="slideChanged($event)">
|
||||||
|
<ng-template let-item="item">
|
||||||
|
<core-loading [hideUntil]="item.loaded" class="safe-area-padding">
|
||||||
<!-- There is data to be synchronized -->
|
<!-- There is data to be synchronized -->
|
||||||
<ion-card class="core-warning-card" *ngIf="hasOffline">
|
<ion-card class="core-warning-card" *ngIf="item.hasOffline">
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-icon name="fas-exclamation-triangle" slot="start" aria-hidden="true"></ion-icon>
|
<ion-icon name="fas-exclamation-triangle" slot="start" aria-hidden="true"></ion-icon>
|
||||||
<ion-label>{{ 'core.hasdatatosync' | translate:{$a: 'core.day' | translate} }}</ion-label>
|
<ion-label>{{ 'core.hasdatatosync' | translate:{$a: 'core.day' | translate} }}</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</ion-card>
|
</ion-card>
|
||||||
|
|
||||||
<core-empty-box *ngIf="!filteredEvents || !filteredEvents.length" icon="fas-calendar" inline="true"
|
<core-empty-box *ngIf="!item.filteredEvents || !item.filteredEvents.length" icon="fas-calendar" inline="true"
|
||||||
[message]="'addon.calendar.noevents' | translate">
|
[message]="'addon.calendar.noevents' | translate">
|
||||||
</core-empty-box>
|
</core-empty-box>
|
||||||
|
|
||||||
<ion-list *ngIf="filteredEvents && filteredEvents.length" class="ion-no-margin">
|
<ion-list *ngIf="item.filteredEvents && item.filteredEvents.length" class="ion-no-margin">
|
||||||
<ng-container *ngFor="let event of filteredEvents">
|
<ng-container *ngFor="let event of item.filteredEvents">
|
||||||
<ion-item class="addon-calendar-event ion-text-wrap" [attr.aria-label]="event.name" (click)="gotoEvent(event.id)"
|
<ion-item class="addon-calendar-event ion-text-wrap" [attr.aria-label]="event.name"
|
||||||
[class.item-dimmed]="event.ispast" [ngClass]="['addon-calendar-eventtype-'+event.eventtype]" button detail="true">
|
(click)="gotoEvent(event.id)" [class.item-dimmed]="event.ispast"
|
||||||
|
[ngClass]="['addon-calendar-eventtype-'+event.eventtype]" button detail="true">
|
||||||
<core-mod-icon *ngIf="event.moduleIcon" [modicon]="event.moduleIcon" slot="start" [showAlt]="false"
|
<core-mod-icon *ngIf="event.moduleIcon" [modicon]="event.moduleIcon" slot="start" [showAlt]="false"
|
||||||
[modname]="event.modname" [componentId]="event.instance">
|
[modname]="event.modname" [componentId]="event.instance">
|
||||||
</core-mod-icon>
|
</core-mod-icon>
|
||||||
<ion-icon *ngIf="event.eventIcon && !event.moduleIcon" [name]="event.eventIcon" slot="start" aria-hidden="true">
|
<ion-icon *ngIf="event.eventIcon && !event.moduleIcon" [name]="event.eventIcon" slot="start"
|
||||||
|
aria-hidden="true">
|
||||||
</ion-icon>
|
</ion-icon>
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<!-- Add the icon title so accessibility tools read it. -->
|
<!-- Add the icon title so accessibility tools read it. -->
|
||||||
|
@ -92,6 +97,9 @@
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ion-list>
|
</ion-list>
|
||||||
</core-loading>
|
</core-loading>
|
||||||
|
</ng-template>
|
||||||
|
</core-swipe-slides>
|
||||||
|
</core-loading>
|
||||||
|
|
||||||
<!-- Create a calendar event. -->
|
<!-- Create a calendar event. -->
|
||||||
<ion-fab slot="fixed" core-fab vertical="bottom" horizontal="end" *ngIf="canCreate && loaded">
|
<ion-fab slot="fixed" core-fab vertical="bottom" horizontal="end" *ngIf="canCreate && loaded">
|
||||||
|
|
|
@ -12,14 +12,14 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core';
|
||||||
import { IonRefresher } from '@ionic/angular';
|
import { IonRefresher } from '@ionic/angular';
|
||||||
import { CoreApp } from '@services/app';
|
import { CoreApp } from '@services/app';
|
||||||
import { CoreEventObserver, CoreEvents } from '@singletons/events';
|
import { CoreEventObserver, CoreEvents } from '@singletons/events';
|
||||||
import { CoreLocalNotifications } from '@services/local-notifications';
|
import { CoreLocalNotifications } from '@services/local-notifications';
|
||||||
import { CoreSites } from '@services/sites';
|
import { CoreSites } from '@services/sites';
|
||||||
import { CoreDomUtils } from '@services/utils/dom';
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
import { CoreTimeUtils } from '@services/utils/time';
|
import { CoreTimeUtils, YearMonthDay } from '@services/utils/time';
|
||||||
import {
|
import {
|
||||||
AddonCalendarProvider,
|
AddonCalendarProvider,
|
||||||
AddonCalendar,
|
AddonCalendar,
|
||||||
|
@ -40,6 +40,12 @@ import { Params } from '@angular/router';
|
||||||
import { Subscription } from 'rxjs';
|
import { Subscription } from 'rxjs';
|
||||||
import { CoreUtils } from '@services/utils/utils';
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
import { CoreConstants } from '@/core/constants';
|
import { CoreConstants } from '@/core/constants';
|
||||||
|
import { CoreSwipeSlidesDynamicItemsManager } from '@classes/items-management/slides-dynamic-items-manager';
|
||||||
|
import { CoreSwipeCurrentItemData, CoreSwipeSlidesComponent } from '@components/swipe-slides/swipe-slides';
|
||||||
|
import {
|
||||||
|
CoreSwipeSlidesDynamicItem,
|
||||||
|
CoreSwipeSlidesDynamicItemsManagerSource,
|
||||||
|
} from '@classes/items-management/slides-dynamic-items-manager-source';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Page that displays the calendar events for a certain day.
|
* Page that displays the calendar events for a certain day.
|
||||||
|
@ -51,20 +57,9 @@ import { CoreConstants } from '@/core/constants';
|
||||||
})
|
})
|
||||||
export class AddonCalendarDayPage implements OnInit, OnDestroy {
|
export class AddonCalendarDayPage implements OnInit, OnDestroy {
|
||||||
|
|
||||||
protected currentSiteId: string;
|
@ViewChild(CoreSwipeSlidesComponent) slides?: CoreSwipeSlidesComponent<PreloadedDay>;
|
||||||
protected year!: number;
|
|
||||||
protected month!: number;
|
|
||||||
protected day!: number;
|
|
||||||
protected categories: { [id: number]: CoreCategoryData } = {};
|
|
||||||
protected events: AddonCalendarEventToDisplay[] = []; // Events (both online and offline).
|
|
||||||
protected onlineEvents: AddonCalendarEventToDisplay[] = [];
|
|
||||||
protected offlineEvents: { [monthId: string]: { [day: number]: AddonCalendarEventToDisplay[] } } =
|
|
||||||
{}; // Offline events classified in month & day.
|
|
||||||
|
|
||||||
protected offlineEditedEventsIds: number[] = []; // IDs of events edited in offline.
|
protected currentSiteId: string;
|
||||||
protected deletedEvents: number[] = []; // Events deleted in offline.
|
|
||||||
protected timeFormat?: string;
|
|
||||||
protected currentTime!: number;
|
|
||||||
|
|
||||||
// Observers.
|
// Observers.
|
||||||
protected newEventObserver: CoreEventObserver;
|
protected newEventObserver: CoreEventObserver;
|
||||||
|
@ -79,16 +74,11 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy {
|
||||||
protected filterChangedObserver: CoreEventObserver;
|
protected filterChangedObserver: CoreEventObserver;
|
||||||
|
|
||||||
periodName?: string;
|
periodName?: string;
|
||||||
filteredEvents: AddonCalendarEventToDisplay [] = [];
|
manager?: CoreSwipeSlidesDynamicItemsManager<PreloadedDay, AddonCalendarDaySlidesItemsManagerSource>;
|
||||||
canCreate = false;
|
|
||||||
courses: Partial<CoreEnrolledCourseData>[] = [];
|
|
||||||
loaded = false;
|
loaded = false;
|
||||||
hasOffline = false;
|
|
||||||
isOnline = false;
|
isOnline = false;
|
||||||
syncIcon = CoreConstants.ICON_LOADING;
|
syncIcon = CoreConstants.ICON_LOADING;
|
||||||
isCurrentDay = false;
|
isCurrentDay = false;
|
||||||
isPastDay = false;
|
|
||||||
currentMoment!: moment.Moment;
|
|
||||||
filter: AddonCalendarFilter = {
|
filter: AddonCalendarFilter = {
|
||||||
filtered: false,
|
filtered: false,
|
||||||
courseId: undefined,
|
courseId: undefined,
|
||||||
|
@ -106,7 +96,9 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy {
|
||||||
if (CoreLocalNotifications.isAvailable()) {
|
if (CoreLocalNotifications.isAvailable()) {
|
||||||
// Re-schedule events if default time changes.
|
// Re-schedule events if default time changes.
|
||||||
this.obsDefaultTimeChange = CoreEvents.on(AddonCalendarProvider.DEFAULT_NOTIFICATION_TIME_CHANGED, () => {
|
this.obsDefaultTimeChange = CoreEvents.on(AddonCalendarProvider.DEFAULT_NOTIFICATION_TIME_CHANGED, () => {
|
||||||
AddonCalendar.scheduleEventsNotifications(this.onlineEvents);
|
this.manager?.getSource().getItems()?.forEach(day => {
|
||||||
|
AddonCalendar.scheduleEventsNotifications(day.onlineEvents);
|
||||||
|
});
|
||||||
}, this.currentSiteId);
|
}, this.currentSiteId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,7 +107,7 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy {
|
||||||
AddonCalendarProvider.NEW_EVENT_EVENT,
|
AddonCalendarProvider.NEW_EVENT_EVENT,
|
||||||
(data) => {
|
(data) => {
|
||||||
if (data && data.eventId) {
|
if (data && data.eventId) {
|
||||||
this.loaded = false;
|
this.manager?.getSource().markAllItemsUnloaded();
|
||||||
this.refreshData(true, true);
|
this.refreshData(true, true);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -124,7 +116,7 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy {
|
||||||
|
|
||||||
// Listen for new event discarded event. When it does, reload the data.
|
// Listen for new event discarded event. When it does, reload the data.
|
||||||
this.discardedObserver = CoreEvents.on(AddonCalendarProvider.NEW_EVENT_DISCARDED_EVENT, () => {
|
this.discardedObserver = CoreEvents.on(AddonCalendarProvider.NEW_EVENT_DISCARDED_EVENT, () => {
|
||||||
this.loaded = false;
|
this.manager?.getSource().markAllItemsUnloaded();
|
||||||
this.refreshData(true, true);
|
this.refreshData(true, true);
|
||||||
}, this.currentSiteId);
|
}, this.currentSiteId);
|
||||||
|
|
||||||
|
@ -133,7 +125,7 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy {
|
||||||
AddonCalendarProvider.EDIT_EVENT_EVENT,
|
AddonCalendarProvider.EDIT_EVENT_EVENT,
|
||||||
(data) => {
|
(data) => {
|
||||||
if (data && data.eventId) {
|
if (data && data.eventId) {
|
||||||
this.loaded = false;
|
this.manager?.getSource().markAllItemsUnloaded();
|
||||||
this.refreshData(true, true);
|
this.refreshData(true, true);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -142,14 +134,16 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy {
|
||||||
|
|
||||||
// Refresh data if calendar events are synchronized automatically.
|
// Refresh data if calendar events are synchronized automatically.
|
||||||
this.syncObserver = CoreEvents.on(AddonCalendarSyncProvider.AUTO_SYNCED, () => {
|
this.syncObserver = CoreEvents.on(AddonCalendarSyncProvider.AUTO_SYNCED, () => {
|
||||||
this.loaded = false;
|
this.manager?.getSource().markAllItemsUnloaded();
|
||||||
this.refreshData(false, true);
|
this.refreshData(false, true);
|
||||||
}, this.currentSiteId);
|
}, this.currentSiteId);
|
||||||
|
|
||||||
// Refresh data if calendar events are synchronized manually but not by this page.
|
// Refresh data if calendar events are synchronized manually but not by this page.
|
||||||
this.manualSyncObserver = CoreEvents.on(AddonCalendarSyncProvider.MANUAL_SYNCED, (data) => {
|
this.manualSyncObserver = CoreEvents.on(AddonCalendarSyncProvider.MANUAL_SYNCED, (data) => {
|
||||||
if (data && (data.source != 'day' || data.year != this.year || data.month != this.month || data.day != this.day)) {
|
const visibleDay = this.slides?.getCurrentItem();
|
||||||
this.loaded = false;
|
if (data && (data.source != 'day' || !visibleDay || data.day === undefined || data.year != visibleDay.year ||
|
||||||
|
data.month != visibleDay.monthNumber || data.day != visibleDay.dayNumber)) {
|
||||||
|
this.manager?.getSource().markAllItemsUnloaded();
|
||||||
this.refreshData(false, true);
|
this.refreshData(false, true);
|
||||||
}
|
}
|
||||||
}, this.currentSiteId);
|
}, this.currentSiteId);
|
||||||
|
@ -160,10 +154,9 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy {
|
||||||
(data) => {
|
(data) => {
|
||||||
if (data && !data.sent) {
|
if (data && !data.sent) {
|
||||||
// Event was deleted in offline. Just mark it as deleted, no need to refresh.
|
// Event was deleted in offline. Just mark it as deleted, no need to refresh.
|
||||||
this.hasOffline = this.markAsDeleted(data.eventId, true) || this.hasOffline;
|
this.manager?.getSource().markAsDeleted(data.eventId, true);
|
||||||
this.deletedEvents.push(data.eventId);
|
|
||||||
} else {
|
} else {
|
||||||
this.loaded = false;
|
this.manager?.getSource().markAllItemsUnloaded();
|
||||||
this.refreshData(false, true);
|
this.refreshData(false, true);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -179,26 +172,7 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark it as undeleted, no need to refresh.
|
// Mark it as undeleted, no need to refresh.
|
||||||
const found = this.markAsDeleted(data.eventId, false);
|
this.manager?.getSource().markAsDeleted(data.eventId, false);
|
||||||
|
|
||||||
// Remove it from the list of deleted events if it's there.
|
|
||||||
const index = this.deletedEvents.indexOf(data.eventId);
|
|
||||||
if (index != -1) {
|
|
||||||
this.deletedEvents.splice(index, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (found) {
|
|
||||||
// The deleted event belongs to current list. Re-calculate "hasOffline".
|
|
||||||
this.hasOffline = false;
|
|
||||||
|
|
||||||
if (this.events.length != this.onlineEvents.length) {
|
|
||||||
this.hasOffline = true;
|
|
||||||
} else {
|
|
||||||
const event = this.events.find((event) => event.deleted || event.offline);
|
|
||||||
|
|
||||||
this.hasOffline = !!event;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
this.currentSiteId,
|
this.currentSiteId,
|
||||||
);
|
);
|
||||||
|
@ -209,9 +183,9 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy {
|
||||||
this.filter = data;
|
this.filter = data;
|
||||||
|
|
||||||
// Course viewed has changed, check if the user can create events for this course calendar.
|
// Course viewed has changed, check if the user can create events for this course calendar.
|
||||||
this.canCreate = await AddonCalendarHelper.canEditEvents(this.filter.courseId);
|
await this.manager?.getSource().loadCanCreate(this.filter.courseId);
|
||||||
|
|
||||||
this.filterEvents();
|
this.manager?.getSource().filterAllDayEvents(this.filter);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -240,17 +214,27 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy {
|
||||||
|
|
||||||
this.filter.filtered = typeof this.filter.courseId != 'undefined' || types.some((name) => !this.filter[name]);
|
this.filter.filtered = typeof this.filter.courseId != 'undefined' || types.some((name) => !this.filter[name]);
|
||||||
|
|
||||||
const now = new Date();
|
const currentDay = CoreTimeUtils.getCurrentDay();
|
||||||
this.year = CoreNavigator.getRouteNumberParam('year') || now.getFullYear();
|
const source = new AddonCalendarDaySlidesItemsManagerSource(this, {
|
||||||
this.month = CoreNavigator.getRouteNumberParam('month') || (now.getMonth() + 1);
|
year: CoreNavigator.getRouteNumberParam('year') ?? currentDay.year,
|
||||||
this.day = CoreNavigator.getRouteNumberParam('day') || now.getDate();
|
monthNumber: CoreNavigator.getRouteNumberParam('month') ?? currentDay.monthNumber,
|
||||||
|
dayNumber: CoreNavigator.getRouteNumberParam('day') ?? currentDay.dayNumber,
|
||||||
|
});
|
||||||
|
this.manager = new CoreSwipeSlidesDynamicItemsManager(source);
|
||||||
|
|
||||||
this.calculateCurrentMoment();
|
|
||||||
this.calculateIsCurrentDay();
|
this.calculateIsCurrentDay();
|
||||||
|
|
||||||
this.fetchData(true);
|
this.fetchData(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get canCreate(): boolean {
|
||||||
|
return this.manager?.getSource().canCreate || false;
|
||||||
|
}
|
||||||
|
|
||||||
|
get timeFormat(): string {
|
||||||
|
return this.manager?.getSource().timeFormat || 'core.strftimetime';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch all the data required for the view.
|
* Fetch all the data required for the view.
|
||||||
*
|
*
|
||||||
|
@ -259,7 +243,6 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy {
|
||||||
* @return Promise resolved when done.
|
* @return Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
async fetchData(sync?: boolean): Promise<void> {
|
async fetchData(sync?: boolean): Promise<void> {
|
||||||
|
|
||||||
this.syncIcon = CoreConstants.ICON_LOADING;
|
this.syncIcon = CoreConstants.ICON_LOADING;
|
||||||
this.isOnline = CoreApp.isOnline();
|
this.isOnline = CoreApp.isOnline();
|
||||||
|
|
||||||
|
@ -268,53 +251,9 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const promises: Promise<void>[] = [];
|
await this.manager?.getSource().fetchData(this.filter.courseId);
|
||||||
|
|
||||||
// Load courses for the popover.
|
await this.manager?.getSource().load(this.manager?.getSelectedItem());
|
||||||
promises.push(CoreCoursesHelper.getCoursesForPopover(this.filter.courseId).then((data) => {
|
|
||||||
this.courses = data.courses;
|
|
||||||
|
|
||||||
return;
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Get categories.
|
|
||||||
promises.push(this.loadCategories());
|
|
||||||
|
|
||||||
// Get offline events.
|
|
||||||
promises.push(AddonCalendarOffline.getAllEditedEvents().then((offlineEvents) => {
|
|
||||||
// Classify them by month & day.
|
|
||||||
this.offlineEvents = AddonCalendarHelper.classifyIntoMonths(offlineEvents);
|
|
||||||
|
|
||||||
// Get the IDs of events edited in offline.
|
|
||||||
this.offlineEditedEventsIds = offlineEvents.filter((event) => event.id > 0).map((event) => event.id);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Get events deleted in offline.
|
|
||||||
promises.push(AddonCalendarOffline.getAllDeletedEventsIds().then((ids) => {
|
|
||||||
this.deletedEvents = ids;
|
|
||||||
|
|
||||||
return;
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Check if user can create events.
|
|
||||||
promises.push(AddonCalendarHelper.canEditEvents(this.filter.courseId).then((canEdit) => {
|
|
||||||
this.canCreate = canEdit;
|
|
||||||
|
|
||||||
return;
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Get user preferences.
|
|
||||||
promises.push(AddonCalendar.getCalendarTimeFormat().then((value) => {
|
|
||||||
this.timeFormat = value;
|
|
||||||
|
|
||||||
return;
|
|
||||||
}));
|
|
||||||
|
|
||||||
await Promise.all(promises);
|
|
||||||
|
|
||||||
await this.fetchEvents();
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
CoreDomUtils.showErrorModalDefault(error, 'addon.calendar.errorloadevents', true);
|
CoreDomUtils.showErrorModalDefault(error, 'addon.calendar.errorloadevents', true);
|
||||||
}
|
}
|
||||||
|
@ -324,104 +263,14 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch the events for current day.
|
* Update data related to day being viewed.
|
||||||
*
|
|
||||||
* @return Promise resolved when done.
|
|
||||||
*/
|
*/
|
||||||
async fetchEvents(): Promise<void> {
|
viewDay(day: YearMonthDay): void {
|
||||||
let result: AddonCalendarCalendarDay;
|
|
||||||
try {
|
|
||||||
// Don't pass courseId and categoryId, we'll filter them locally.
|
|
||||||
result = await AddonCalendar.getDayEvents(this.year, this.month, this.day);
|
|
||||||
this.onlineEvents = await Promise.all(result.events.map((event) => AddonCalendarHelper.formatEventData(event)));
|
|
||||||
} catch (error) {
|
|
||||||
if (CoreApp.isOnline()) {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
// Allow navigating to non-cached days in offline (behave as if using emergency cache).
|
|
||||||
this.onlineEvents = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate the period name. We don't use the one in result because it's in server's language.
|
|
||||||
this.periodName = CoreTimeUtils.userDate(
|
this.periodName = CoreTimeUtils.userDate(
|
||||||
new Date(this.year, this.month - 1, this.day).getTime(),
|
new Date(day.year, day.monthNumber - 1, day.dayNumber).getTime(),
|
||||||
'core.strftimedaydate',
|
'core.strftimedaydate',
|
||||||
);
|
);
|
||||||
|
|
||||||
// Schedule notifications for the events retrieved (only future events will be scheduled).
|
|
||||||
AddonCalendar.scheduleEventsNotifications(this.onlineEvents);
|
|
||||||
// Merge the online events with offline data.
|
|
||||||
this.events = this.mergeEvents();
|
|
||||||
// Filter events by course.
|
|
||||||
this.filterEvents();
|
|
||||||
this.calculateIsCurrentDay();
|
this.calculateIsCurrentDay();
|
||||||
// Re-calculate the formatted time so it uses the device date.
|
|
||||||
const dayTime = this.currentMoment.unix() * 1000;
|
|
||||||
|
|
||||||
const promises = this.events.map((event) => {
|
|
||||||
event.ispast = this.isPastDay || (this.isCurrentDay && this.isEventPast(event));
|
|
||||||
|
|
||||||
return AddonCalendar.formatEventTime(event, this.timeFormat, true, dayTime).then((time) => {
|
|
||||||
event.formattedtime = time;
|
|
||||||
|
|
||||||
return;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
await Promise.all(promises);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Merge online events with the offline events of that period.
|
|
||||||
*
|
|
||||||
* @return Merged events.
|
|
||||||
*/
|
|
||||||
protected mergeEvents(): AddonCalendarEventToDisplay[] {
|
|
||||||
this.hasOffline = false;
|
|
||||||
|
|
||||||
if (!Object.keys(this.offlineEvents).length && !this.deletedEvents.length) {
|
|
||||||
// No offline events, nothing to merge.
|
|
||||||
return this.onlineEvents;
|
|
||||||
}
|
|
||||||
|
|
||||||
const monthOfflineEvents = this.offlineEvents[AddonCalendarHelper.getMonthId(this.year, this.month)];
|
|
||||||
const dayOfflineEvents = monthOfflineEvents && monthOfflineEvents[this.day];
|
|
||||||
let result = this.onlineEvents;
|
|
||||||
|
|
||||||
if (this.deletedEvents.length) {
|
|
||||||
// Mark as deleted the events that were deleted in offline.
|
|
||||||
result.forEach((event) => {
|
|
||||||
event.deleted = this.deletedEvents.indexOf(event.id) != -1;
|
|
||||||
|
|
||||||
if (event.deleted) {
|
|
||||||
this.hasOffline = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.offlineEditedEventsIds.length) {
|
|
||||||
// Remove the online events that were modified in offline.
|
|
||||||
result = result.filter((event) => this.offlineEditedEventsIds.indexOf(event.id) == -1);
|
|
||||||
|
|
||||||
if (result.length != this.onlineEvents.length) {
|
|
||||||
this.hasOffline = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dayOfflineEvents && dayOfflineEvents.length) {
|
|
||||||
// Add the offline events (either new or edited).
|
|
||||||
this.hasOffline = true;
|
|
||||||
result = AddonCalendarHelper.sortEvents(result.concat(dayOfflineEvents));
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Filter events based on the filter popover.
|
|
||||||
*/
|
|
||||||
protected filterEvents(): void {
|
|
||||||
this.filteredEvents = AddonCalendarHelper.getFilteredEvents(this.events, this.filter, this.categories);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -452,37 +301,12 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy {
|
||||||
async refreshData(sync?: boolean, afterChange?: boolean): Promise<void> {
|
async refreshData(sync?: boolean, afterChange?: boolean): Promise<void> {
|
||||||
this.syncIcon = CoreConstants.ICON_LOADING;
|
this.syncIcon = CoreConstants.ICON_LOADING;
|
||||||
|
|
||||||
const promises: Promise<void>[] = [];
|
const visibleDay = this.slides?.getCurrentItem() || null;
|
||||||
|
|
||||||
// Don't invalidate day events after a change, it has already been handled.
|
// Don't invalidate day events after a change, it has already been handled.
|
||||||
if (!afterChange) {
|
await this.manager?.getSource().invalidateContent(visibleDay, !afterChange);
|
||||||
promises.push(AddonCalendar.invalidateDayEvents(this.year, this.month, this.day));
|
|
||||||
}
|
|
||||||
promises.push(AddonCalendar.invalidateAllowedEventTypes());
|
|
||||||
promises.push(CoreCourses.invalidateCategories(0, true));
|
|
||||||
promises.push(AddonCalendar.invalidateTimeFormat());
|
|
||||||
|
|
||||||
await Promise.all(promises).finally(() =>
|
await this.fetchData(sync);
|
||||||
this.fetchData(sync));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load categories to be able to filter events.
|
|
||||||
*
|
|
||||||
* @return Promise resolved when done.
|
|
||||||
*/
|
|
||||||
protected async loadCategories(): Promise<void> {
|
|
||||||
try {
|
|
||||||
const cats = await CoreCourses.getCategories(0, true);
|
|
||||||
this.categories = {};
|
|
||||||
|
|
||||||
// Index categories by ID.
|
|
||||||
cats.forEach((category) => {
|
|
||||||
this.categories[category.id] = category;
|
|
||||||
});
|
|
||||||
} catch {
|
|
||||||
// Ignore errors.
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -501,11 +325,16 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy {
|
||||||
|
|
||||||
if (result.updated) {
|
if (result.updated) {
|
||||||
// Trigger a manual sync event.
|
// Trigger a manual sync event.
|
||||||
|
const visibleDay = this.slides?.getCurrentItem();
|
||||||
result.source = 'day';
|
result.source = 'day';
|
||||||
result.day = this.day;
|
|
||||||
result.month = this.month;
|
|
||||||
result.year = this.year;
|
|
||||||
|
|
||||||
|
if (visibleDay) {
|
||||||
|
result.day = visibleDay.dayNumber;
|
||||||
|
result.month = visibleDay.monthNumber;
|
||||||
|
result.year = visibleDay.year;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.manager?.getSource().markAllItemsUnloaded();
|
||||||
CoreEvents.trigger(AddonCalendarSyncProvider.MANUAL_SYNCED, result, this.currentSiteId);
|
CoreEvents.trigger(AddonCalendarSyncProvider.MANUAL_SYNCED, result, this.currentSiteId);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -533,7 +362,7 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy {
|
||||||
await CoreDomUtils.openPopover({
|
await CoreDomUtils.openPopover({
|
||||||
component: AddonCalendarFilterPopoverComponent,
|
component: AddonCalendarFilterPopoverComponent,
|
||||||
componentProps: {
|
componentProps: {
|
||||||
courses: this.courses,
|
courses: this.manager?.getSource().courses,
|
||||||
filter: this.filter,
|
filter: this.filter,
|
||||||
},
|
},
|
||||||
event,
|
event,
|
||||||
|
@ -551,7 +380,12 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy {
|
||||||
if (!eventId) {
|
if (!eventId) {
|
||||||
// It's a new event, set the time.
|
// It's a new event, set the time.
|
||||||
eventId = 0;
|
eventId = 0;
|
||||||
params.timestamp = moment().year(this.year).month(this.month - 1).date(this.day).unix() * 1000;
|
|
||||||
|
const visibleDay = this.slides?.getCurrentItem();
|
||||||
|
if (visibleDay) {
|
||||||
|
params.timestamp = moment().year(visibleDay.year).month(visibleDay.monthNumber - 1).date(visibleDay.dayNumber)
|
||||||
|
.unix() * 1000;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.filter.courseId) {
|
if (this.filter.courseId) {
|
||||||
|
@ -561,141 +395,76 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy {
|
||||||
CoreNavigator.navigateToSitePath(`/calendar/edit/${eventId}`, { params });
|
CoreNavigator.navigateToSitePath(`/calendar/edit/${eventId}`, { params });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculate current moment.
|
|
||||||
*/
|
|
||||||
calculateCurrentMoment(): void {
|
|
||||||
this.currentMoment = moment().year(this.year).month(this.month - 1).date(this.day);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if user is viewing the current day.
|
* Check if user is viewing the current day.
|
||||||
*/
|
*/
|
||||||
calculateIsCurrentDay(): void {
|
calculateIsCurrentDay(): void {
|
||||||
const now = new Date();
|
const visibleDay = this.slides?.getCurrentItem();
|
||||||
|
if (!visibleDay) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.currentTime = CoreTimeUtils.timestamp();
|
this.isCurrentDay = visibleDay.isCurrentDay;
|
||||||
|
}
|
||||||
|
|
||||||
this.isCurrentDay = this.year == now.getFullYear() && this.month == now.getMonth() + 1 && this.day == now.getDate();
|
/**
|
||||||
this.isPastDay = this.year < now.getFullYear() || (this.year == now.getFullYear() && this.month < now.getMonth()) ||
|
* Check whether visible day has offline data.
|
||||||
(this.year == now.getFullYear() && this.month == now.getMonth() + 1 && this.day < now.getDate());
|
*
|
||||||
|
* @return Whether visible day has offline data.
|
||||||
|
*/
|
||||||
|
visibleDayHasOffline(): boolean {
|
||||||
|
const visibleDay = this.slides?.getCurrentItem();
|
||||||
|
|
||||||
|
return !!visibleDay && visibleDay.hasOffline;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Go to current day.
|
* Go to current day.
|
||||||
*/
|
*/
|
||||||
async goToCurrentDay(): Promise<void> {
|
async goToCurrentDay(): Promise<void> {
|
||||||
const now = new Date();
|
const manager = this.manager;
|
||||||
const initialDay = this.day;
|
const slides = this.slides;
|
||||||
const initialMonth = this.month;
|
if (!manager || !slides) {
|
||||||
const initialYear = this.year;
|
return;
|
||||||
|
}
|
||||||
this.day = now.getDate();
|
|
||||||
this.month = now.getMonth() + 1;
|
|
||||||
this.year = now.getFullYear();
|
|
||||||
this.calculateCurrentMoment();
|
|
||||||
|
|
||||||
|
const currentDay = CoreTimeUtils.getCurrentDay();
|
||||||
this.loaded = false;
|
this.loaded = false;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.fetchEvents();
|
// Make sure the day is loaded.
|
||||||
|
await manager.getSource().loadItem(currentDay);
|
||||||
|
|
||||||
|
slides.slideToItem(currentDay);
|
||||||
this.isCurrentDay = true;
|
this.isCurrentDay = true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
CoreDomUtils.showErrorModalDefault(error, 'addon.calendar.errorloadevents', true);
|
CoreDomUtils.showErrorModalDefault(error, 'addon.calendar.errorloadevents', true);
|
||||||
|
} finally {
|
||||||
this.year = initialYear;
|
|
||||||
this.month = initialMonth;
|
|
||||||
this.day = initialDay;
|
|
||||||
this.calculateCurrentMoment();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.loaded = true;
|
this.loaded = true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load next day.
|
* Load next day.
|
||||||
*/
|
*/
|
||||||
async loadNext(): Promise<void> {
|
async loadNext(): Promise<void> {
|
||||||
this.increaseDay();
|
this.slides?.slideNext();
|
||||||
|
|
||||||
this.loaded = false;
|
|
||||||
|
|
||||||
try {
|
|
||||||
await this.fetchEvents();
|
|
||||||
} catch (error) {
|
|
||||||
CoreDomUtils.showErrorModalDefault(error, 'addon.calendar.errorloadevents', true);
|
|
||||||
this.decreaseDay();
|
|
||||||
}
|
|
||||||
this.loaded = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load previous day.
|
* Load previous day.
|
||||||
*/
|
*/
|
||||||
async loadPrevious(): Promise<void> {
|
async loadPrevious(): Promise<void> {
|
||||||
this.decreaseDay();
|
this.slides?.slidePrev();
|
||||||
|
|
||||||
this.loaded = false;
|
|
||||||
|
|
||||||
try {
|
|
||||||
await this.fetchEvents();
|
|
||||||
} catch (error) {
|
|
||||||
CoreDomUtils.showErrorModalDefault(error, 'addon.calendar.errorloadevents', true);
|
|
||||||
this.increaseDay();
|
|
||||||
}
|
|
||||||
this.loaded = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decrease the current day.
|
* Slide has changed.
|
||||||
*/
|
|
||||||
protected decreaseDay(): void {
|
|
||||||
this.currentMoment.subtract(1, 'day');
|
|
||||||
|
|
||||||
this.year = this.currentMoment.year();
|
|
||||||
this.month = this.currentMoment.month() + 1;
|
|
||||||
this.day = this.currentMoment.date();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Increase the current day.
|
|
||||||
*/
|
|
||||||
protected increaseDay(): void {
|
|
||||||
this.currentMoment.add(1, 'day');
|
|
||||||
|
|
||||||
this.year = this.currentMoment.year();
|
|
||||||
this.month = this.currentMoment.month() + 1;
|
|
||||||
this.day = this.currentMoment.date();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find an event and mark it as deleted.
|
|
||||||
*
|
*
|
||||||
* @param eventId Event ID.
|
* @param data Data about new item.
|
||||||
* @param deleted Whether to mark it as deleted or not.
|
|
||||||
* @return Whether the event was found.
|
|
||||||
*/
|
*/
|
||||||
protected markAsDeleted(eventId: number, deleted: boolean): boolean {
|
slideChanged(data: CoreSwipeCurrentItemData<PreloadedDay>): void {
|
||||||
const event = this.onlineEvents.find((event) => event.id == eventId);
|
this.viewDay(data.item);
|
||||||
|
|
||||||
if (event) {
|
|
||||||
event.deleted = deleted;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns if the event is in the past or not.
|
|
||||||
*
|
|
||||||
* @param event Event object.
|
|
||||||
* @return True if it's in the past.
|
|
||||||
*/
|
|
||||||
isEventPast(event: AddonCalendarEventToDisplay): boolean {
|
|
||||||
return (event.timestart + event.timeduration) < this.currentTime;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -715,3 +484,359 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Preloaded month.
|
||||||
|
*/
|
||||||
|
type PreloadedDay = YearMonthDay & CoreSwipeSlidesDynamicItem & {
|
||||||
|
events: AddonCalendarEventToDisplay[]; // Events (both online and offline).
|
||||||
|
onlineEvents: AddonCalendarEventToDisplay[];
|
||||||
|
filteredEvents: AddonCalendarEventToDisplay[];
|
||||||
|
isCurrentDay: boolean;
|
||||||
|
isPastDay: boolean;
|
||||||
|
hasOffline: boolean; // Whether the day has offline data.
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper to manage swiping within days.
|
||||||
|
*/
|
||||||
|
class AddonCalendarDaySlidesItemsManagerSource extends CoreSwipeSlidesDynamicItemsManagerSource<PreloadedDay> {
|
||||||
|
|
||||||
|
courses: Partial<CoreEnrolledCourseData>[] = [];
|
||||||
|
// Offline events classified in month & day.
|
||||||
|
offlineEvents: Record<string, Record<number, AddonCalendarEventToDisplay[]>> = {};
|
||||||
|
offlineEditedEventsIds: number[] = []; // IDs of events edited in offline.
|
||||||
|
categories: { [id: number]: CoreCategoryData } = {};
|
||||||
|
deletedEvents: number[] = []; // Events deleted in offline.
|
||||||
|
timeFormat?: string;
|
||||||
|
canCreate = false;
|
||||||
|
|
||||||
|
protected dayPage: AddonCalendarDayPage;
|
||||||
|
protected categoriesRetrieved = false;
|
||||||
|
|
||||||
|
constructor(page: AddonCalendarDayPage, initialDay: YearMonthDay) {
|
||||||
|
super(initialDay);
|
||||||
|
|
||||||
|
this.dayPage = page;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch data.
|
||||||
|
*
|
||||||
|
* @param courseId Current selected course id (if any).
|
||||||
|
* @return Promise resolved when done.
|
||||||
|
*/
|
||||||
|
async fetchData(courseId?: number): Promise<void> {
|
||||||
|
await Promise.all([
|
||||||
|
this.loadCourses(courseId),
|
||||||
|
this.loadCanCreate(courseId),
|
||||||
|
this.loadCategories(),
|
||||||
|
this.loadOfflineEvents(),
|
||||||
|
this.loadOfflineDeletedEvents(),
|
||||||
|
this.loadTimeFormat(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter all loaded days events based on the filter popover.
|
||||||
|
*
|
||||||
|
* @param filter Filter to apply.
|
||||||
|
*/
|
||||||
|
filterAllDayEvents(filter: AddonCalendarFilter): void {
|
||||||
|
this.getItems()?.forEach(day => this.filterEvents(day, filter));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter events of a certain day based on the filter popover.
|
||||||
|
*
|
||||||
|
* @param day Day with the events.
|
||||||
|
* @param filter Filter to apply.
|
||||||
|
*/
|
||||||
|
filterEvents(day: PreloadedDay, filter: AddonCalendarFilter): void {
|
||||||
|
day.filteredEvents = AddonCalendarHelper.getFilteredEvents(day.events, filter, this.categories);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load courses.
|
||||||
|
*
|
||||||
|
* @param courseId Current selected course id (if any).
|
||||||
|
* @return Promise resolved when done.
|
||||||
|
*/
|
||||||
|
async loadCourses(courseId?: number): Promise<void> {
|
||||||
|
const data = await CoreCoursesHelper.getCoursesForPopover(courseId);
|
||||||
|
|
||||||
|
this.courses = data.courses;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load whether user can create events.
|
||||||
|
*
|
||||||
|
* @param courseId Current selected course id (if any).
|
||||||
|
* @return Promise resolved when done.
|
||||||
|
*/
|
||||||
|
async loadCanCreate(courseId?: number): Promise<void> {
|
||||||
|
this.canCreate = await AddonCalendarHelper.canEditEvents(courseId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load categories to be able to filter events.
|
||||||
|
*
|
||||||
|
* @return Promise resolved when done.
|
||||||
|
*/
|
||||||
|
async loadCategories(): Promise<void> {
|
||||||
|
if (this.categoriesRetrieved) {
|
||||||
|
// Already retrieved, stop.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const cats = await CoreCourses.getCategories(0, true);
|
||||||
|
this.categoriesRetrieved = true;
|
||||||
|
this.categories = {};
|
||||||
|
|
||||||
|
// Index categories by ID.
|
||||||
|
cats.forEach((category) => {
|
||||||
|
this.categories[category.id] = category;
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
// Ignore errors.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load events created or edited in offline.
|
||||||
|
*
|
||||||
|
* @return Promise resolved when done.
|
||||||
|
*/
|
||||||
|
async loadOfflineEvents(): Promise<void> {
|
||||||
|
// Get offline events.
|
||||||
|
const events = await AddonCalendarOffline.getAllEditedEvents();
|
||||||
|
|
||||||
|
// Classify them by month & day.
|
||||||
|
this.offlineEvents = AddonCalendarHelper.classifyIntoMonths(events);
|
||||||
|
|
||||||
|
// Get the IDs of events edited in offline.
|
||||||
|
this.offlineEditedEventsIds = events.filter((event) => event.id > 0).map((event) => event.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load events deleted in offline.
|
||||||
|
*
|
||||||
|
* @return Promise resolved when done.
|
||||||
|
*/
|
||||||
|
async loadOfflineDeletedEvents(): Promise<void> {
|
||||||
|
this.deletedEvents = await AddonCalendarOffline.getAllDeletedEventsIds();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load time format.
|
||||||
|
*
|
||||||
|
* @return Promise resolved when done.
|
||||||
|
*/
|
||||||
|
async loadTimeFormat(): Promise<void> {
|
||||||
|
this.timeFormat = await AddonCalendar.getCalendarTimeFormat();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
getItemId(item: YearMonthDay): string | number {
|
||||||
|
return AddonCalendarHelper.getDayId(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
getPreviousItem(item: YearMonthDay): YearMonthDay | null {
|
||||||
|
return CoreTimeUtils.getPreviousDay(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
getNextItem(item: YearMonthDay): YearMonthDay | null {
|
||||||
|
return CoreTimeUtils.getNextDay(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
async loadItemData(day: YearMonthDay, preload = false): Promise<PreloadedDay | null> {
|
||||||
|
const preloadedDay: PreloadedDay = {
|
||||||
|
...day,
|
||||||
|
hasOffline: false,
|
||||||
|
events: [],
|
||||||
|
onlineEvents: [],
|
||||||
|
filteredEvents: [],
|
||||||
|
isCurrentDay: CoreTimeUtils.isSameDay(day, CoreTimeUtils.getCurrentDay()),
|
||||||
|
isPastDay: CoreTimeUtils.isPastDay(day),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (preload) {
|
||||||
|
return preloadedDay;
|
||||||
|
}
|
||||||
|
|
||||||
|
let result: AddonCalendarCalendarDay;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Don't pass courseId and categoryId, we'll filter them locally.
|
||||||
|
result = await AddonCalendar.getDayEvents(day.year, day.monthNumber, day.dayNumber);
|
||||||
|
preloadedDay.onlineEvents = await Promise.all(
|
||||||
|
result.events.map((event) => AddonCalendarHelper.formatEventData(event)),
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
// Allow navigating to non-cached days in offline (behave as if using emergency cache).
|
||||||
|
if (CoreApp.isOnline()) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Schedule notifications for the events retrieved (only future events will be scheduled).
|
||||||
|
AddonCalendar.scheduleEventsNotifications(preloadedDay.onlineEvents);
|
||||||
|
|
||||||
|
// Merge the online events with offline data.
|
||||||
|
preloadedDay.events = this.mergeEvents(preloadedDay);
|
||||||
|
|
||||||
|
// Filter events by course.
|
||||||
|
this.filterEvents(preloadedDay, this.dayPage.filter);
|
||||||
|
|
||||||
|
// Re-calculate the formatted time so it uses the device date.
|
||||||
|
const dayTime = moment().year(day.year).month(day.monthNumber - 1).date(day.dayNumber).unix() * 1000;
|
||||||
|
const currentTime = CoreTimeUtils.timestamp();
|
||||||
|
|
||||||
|
const promises = preloadedDay.events.map(async (event) => {
|
||||||
|
event.ispast = preloadedDay.isPastDay || (preloadedDay.isCurrentDay && this.isEventPast(event, currentTime));
|
||||||
|
event.formattedtime = await AddonCalendar.formatEventTime(event, this.dayPage.timeFormat, true, dayTime);
|
||||||
|
});
|
||||||
|
|
||||||
|
await Promise.all(promises);
|
||||||
|
|
||||||
|
return preloadedDay;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns if the event is in the past or not.
|
||||||
|
*
|
||||||
|
* @param event Event object.
|
||||||
|
* @param currentTime Current time.
|
||||||
|
* @return True if it's in the past.
|
||||||
|
*/
|
||||||
|
isEventPast(event: AddonCalendarEventToDisplay, currentTime: number): boolean {
|
||||||
|
return (event.timestart + event.timeduration) < currentTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merge online events with the offline events of that period.
|
||||||
|
*
|
||||||
|
* @param day Day with the events.
|
||||||
|
* @return Merged events.
|
||||||
|
*/
|
||||||
|
mergeEvents(day: PreloadedDay): AddonCalendarEventToDisplay[] {
|
||||||
|
day.hasOffline = false;
|
||||||
|
|
||||||
|
if (!Object.keys(this.offlineEvents).length && !this.deletedEvents.length) {
|
||||||
|
// No offline events, nothing to merge.
|
||||||
|
return day.onlineEvents;
|
||||||
|
}
|
||||||
|
|
||||||
|
const monthOfflineEvents = this.offlineEvents[AddonCalendarHelper.getMonthId(day)];
|
||||||
|
const dayOfflineEvents = monthOfflineEvents && monthOfflineEvents[day.dayNumber];
|
||||||
|
let result = day.onlineEvents;
|
||||||
|
|
||||||
|
if (this.deletedEvents.length) {
|
||||||
|
// Mark as deleted the events that were deleted in offline.
|
||||||
|
result.forEach((event) => {
|
||||||
|
event.deleted = this.deletedEvents.indexOf(event.id) != -1;
|
||||||
|
|
||||||
|
if (event.deleted) {
|
||||||
|
day.hasOffline = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.offlineEditedEventsIds.length) {
|
||||||
|
// Remove the online events that were modified in offline.
|
||||||
|
result = result.filter((event) => this.offlineEditedEventsIds.indexOf(event.id) == -1);
|
||||||
|
|
||||||
|
if (result.length != day.onlineEvents.length) {
|
||||||
|
day.hasOffline = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dayOfflineEvents && dayOfflineEvents.length) {
|
||||||
|
// Add the offline events (either new or edited).
|
||||||
|
day.hasOffline = true;
|
||||||
|
result = AddonCalendarHelper.sortEvents(result.concat(dayOfflineEvents));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalidate content.
|
||||||
|
*
|
||||||
|
* @param visibleDay The current visible day.
|
||||||
|
* @param invalidateDayEvents Whether to invalidate visible day events.
|
||||||
|
* @return Promise resolved when done.
|
||||||
|
*/
|
||||||
|
async invalidateContent(visibleDay: PreloadedDay | null, invalidateDayEvents?: boolean): Promise<void> {
|
||||||
|
const promises: Promise<void>[] = [];
|
||||||
|
|
||||||
|
if (invalidateDayEvents && visibleDay) {
|
||||||
|
promises.push(AddonCalendar.invalidateDayEvents(visibleDay.year, visibleDay.monthNumber, visibleDay.dayNumber));
|
||||||
|
}
|
||||||
|
promises.push(AddonCalendar.invalidateAllowedEventTypes());
|
||||||
|
promises.push(CoreCourses.invalidateCategories(0, true));
|
||||||
|
promises.push(AddonCalendar.invalidateTimeFormat());
|
||||||
|
|
||||||
|
this.categoriesRetrieved = false; // Get categories again.
|
||||||
|
|
||||||
|
if (visibleDay) {
|
||||||
|
visibleDay.dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(promises);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find an event and mark it as deleted.
|
||||||
|
*
|
||||||
|
* @param eventId Event ID.
|
||||||
|
* @param deleted Whether to mark it as deleted or not.
|
||||||
|
*/
|
||||||
|
markAsDeleted(eventId: number, deleted: boolean): void {
|
||||||
|
// Mark the event as deleted or not.
|
||||||
|
this.getItems()?.some(day => {
|
||||||
|
const event = day.onlineEvents.find((event) => event.id == eventId);
|
||||||
|
|
||||||
|
if (!event) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
event.deleted = deleted;
|
||||||
|
|
||||||
|
if (deleted) {
|
||||||
|
day.hasOffline = true;
|
||||||
|
} else {
|
||||||
|
// Re-calculate "hasOffline".
|
||||||
|
day.hasOffline = day.events.length != day.onlineEvents.length ||
|
||||||
|
day.events.some((event) => event.deleted || event.offline);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add it or remove it from the list of deleted events.
|
||||||
|
if (deleted) {
|
||||||
|
if (!this.deletedEvents.includes(eventId)) {
|
||||||
|
this.deletedEvents.push(eventId);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const index = this.deletedEvents.indexOf(eventId);
|
||||||
|
if (index != -1) {
|
||||||
|
this.deletedEvents.splice(index, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -6,4 +6,8 @@
|
||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
core-swipe-slides {
|
||||||
|
--swipe-slides-min-height: calc(100% - 52px);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -92,7 +92,7 @@ export class AddonCalendarIndexPage implements OnInit, OnDestroy {
|
||||||
(data) => {
|
(data) => {
|
||||||
if (data && data.eventId) {
|
if (data && data.eventId) {
|
||||||
this.loaded = false;
|
this.loaded = false;
|
||||||
this.refreshData(true, false);
|
this.refreshData(true, false, true);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
this.currentSiteId,
|
this.currentSiteId,
|
||||||
|
@ -101,7 +101,7 @@ export class AddonCalendarIndexPage implements OnInit, OnDestroy {
|
||||||
// Listen for new event discarded event. When it does, reload the data.
|
// Listen for new event discarded event. When it does, reload the data.
|
||||||
this.discardedObserver = CoreEvents.on(AddonCalendarProvider.NEW_EVENT_DISCARDED_EVENT, () => {
|
this.discardedObserver = CoreEvents.on(AddonCalendarProvider.NEW_EVENT_DISCARDED_EVENT, () => {
|
||||||
this.loaded = false;
|
this.loaded = false;
|
||||||
this.refreshData(true, false);
|
this.refreshData(true, false, true);
|
||||||
}, this.currentSiteId);
|
}, this.currentSiteId);
|
||||||
|
|
||||||
// Listen for events edited. When an event is edited, reload the data.
|
// Listen for events edited. When an event is edited, reload the data.
|
||||||
|
@ -110,7 +110,7 @@ export class AddonCalendarIndexPage implements OnInit, OnDestroy {
|
||||||
(data) => {
|
(data) => {
|
||||||
if (data && data.eventId) {
|
if (data && data.eventId) {
|
||||||
this.loaded = false;
|
this.loaded = false;
|
||||||
this.refreshData(true, false);
|
this.refreshData(true, false, true);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
this.currentSiteId,
|
this.currentSiteId,
|
||||||
|
@ -119,21 +119,21 @@ export class AddonCalendarIndexPage implements OnInit, OnDestroy {
|
||||||
// Refresh data if calendar events are synchronized automatically.
|
// Refresh data if calendar events are synchronized automatically.
|
||||||
this.syncObserver = CoreEvents.on(AddonCalendarSyncProvider.AUTO_SYNCED, () => {
|
this.syncObserver = CoreEvents.on(AddonCalendarSyncProvider.AUTO_SYNCED, () => {
|
||||||
this.loaded = false;
|
this.loaded = false;
|
||||||
this.refreshData(false, false);
|
this.refreshData(false, false, true);
|
||||||
}, this.currentSiteId);
|
}, this.currentSiteId);
|
||||||
|
|
||||||
// Refresh data if calendar events are synchronized manually but not by this page.
|
// Refresh data if calendar events are synchronized manually but not by this page.
|
||||||
this.manualSyncObserver = CoreEvents.on(AddonCalendarSyncProvider.MANUAL_SYNCED, (data) => {
|
this.manualSyncObserver = CoreEvents.on(AddonCalendarSyncProvider.MANUAL_SYNCED, (data) => {
|
||||||
if (data && data.source != 'index') {
|
if (data && data.source != 'index') {
|
||||||
this.loaded = false;
|
this.loaded = false;
|
||||||
this.refreshData(false, false);
|
this.refreshData(false, false, true);
|
||||||
}
|
}
|
||||||
}, this.currentSiteId);
|
}, this.currentSiteId);
|
||||||
|
|
||||||
// Update the events when an event is deleted.
|
// Update the events when an event is deleted.
|
||||||
this.deleteEventObserver = CoreEvents.on(AddonCalendarProvider.DELETED_EVENT_EVENT, () => {
|
this.deleteEventObserver = CoreEvents.on(AddonCalendarProvider.DELETED_EVENT_EVENT, () => {
|
||||||
this.loaded = false;
|
this.loaded = false;
|
||||||
this.refreshData(false, false);
|
this.refreshData(false, false, true);
|
||||||
}, this.currentSiteId);
|
}, this.currentSiteId);
|
||||||
|
|
||||||
// Update the "hasOffline" property if an event deleted in offline is restored.
|
// Update the "hasOffline" property if an event deleted in offline is restored.
|
||||||
|
@ -278,7 +278,7 @@ export class AddonCalendarIndexPage implements OnInit, OnDestroy {
|
||||||
* @param afterChange Whether the refresh is done after an event has changed or has been synced.
|
* @param afterChange Whether the refresh is done after an event has changed or has been synced.
|
||||||
* @return Promise resolved when done.
|
* @return Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
async refreshData(sync = false, showErrors = false): Promise<void> {
|
async refreshData(sync = false, showErrors = false, afterChange = false): Promise<void> {
|
||||||
this.syncIcon = CoreConstants.ICON_LOADING;
|
this.syncIcon = CoreConstants.ICON_LOADING;
|
||||||
|
|
||||||
const promises: Promise<void>[] = [];
|
const promises: Promise<void>[] = [];
|
||||||
|
@ -287,7 +287,7 @@ export class AddonCalendarIndexPage implements OnInit, OnDestroy {
|
||||||
|
|
||||||
// Refresh the sub-component.
|
// Refresh the sub-component.
|
||||||
if (this.showCalendar && this.calendarComponent) {
|
if (this.showCalendar && this.calendarComponent) {
|
||||||
promises.push(this.calendarComponent.refreshData());
|
promises.push(this.calendarComponent.refreshData(afterChange));
|
||||||
} else if (!this.showCalendar && this.upcomingEventsComponent) {
|
} else if (!this.showCalendar && this.upcomingEventsComponent) {
|
||||||
promises.push(this.upcomingEventsComponent.refreshData());
|
promises.push(this.upcomingEventsComponent.refreshData());
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,7 @@ import { AddonCalendarSyncInvalidateEvent } from './calendar-sync';
|
||||||
import { AddonCalendarOfflineEventDBRecord } from './database/calendar-offline';
|
import { AddonCalendarOfflineEventDBRecord } from './database/calendar-offline';
|
||||||
import { CoreCategoryData } from '@features/courses/services/courses';
|
import { CoreCategoryData } from '@features/courses/services/courses';
|
||||||
import { AddonCalendarReminderDBRecord } from './database/calendar';
|
import { AddonCalendarReminderDBRecord } from './database/calendar';
|
||||||
|
import { YearAndMonth, YearMonthDay } from '@services/utils/time';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Context levels enumeration.
|
* Context levels enumeration.
|
||||||
|
@ -140,7 +141,7 @@ export class AddonCalendarHelperProvider {
|
||||||
|
|
||||||
// Add the event to all the days it lasts.
|
// Add the event to all the days it lasts.
|
||||||
while (!treatedDay.isAfter(endDay, 'day')) {
|
while (!treatedDay.isAfter(endDay, 'day')) {
|
||||||
const monthId = this.getMonthId(treatedDay.year(), treatedDay.month() + 1);
|
const monthId = this.getMonthId({ year: treatedDay.year(), monthNumber: treatedDay.month() + 1 });
|
||||||
const day = treatedDay.date();
|
const day = treatedDay.date();
|
||||||
|
|
||||||
if (!result[monthId]) {
|
if (!result[monthId]) {
|
||||||
|
@ -364,14 +365,23 @@ export class AddonCalendarHelperProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the month "id" (year + month).
|
* Get the month "id".
|
||||||
*
|
*
|
||||||
* @param year Year.
|
* @param month Month data.
|
||||||
* @param month Month.
|
|
||||||
* @return The "id".
|
* @return The "id".
|
||||||
*/
|
*/
|
||||||
getMonthId(year: number, month: number): string {
|
getMonthId(month: YearAndMonth): string {
|
||||||
return year + '#' + month;
|
return `${month.year}#${month.monthNumber}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the day "id".
|
||||||
|
*
|
||||||
|
* @param day Day data.
|
||||||
|
* @return The "id".
|
||||||
|
*/
|
||||||
|
getDayId(day: YearMonthDay): string {
|
||||||
|
return `${day.year}#${day.monthNumber}#${day.dayNumber}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -650,7 +660,7 @@ export class AddonCalendarHelperProvider {
|
||||||
fetchTimestarts.map((fetchTime) => {
|
fetchTimestarts.map((fetchTime) => {
|
||||||
const day = moment(new Date(fetchTime * 1000));
|
const day = moment(new Date(fetchTime * 1000));
|
||||||
|
|
||||||
const monthId = this.getMonthId(day.year(), day.month() + 1);
|
const monthId = this.getMonthId({ year: day.year(), monthNumber: day.month() + 1 });
|
||||||
if (!treatedMonths[monthId]) {
|
if (!treatedMonths[monthId]) {
|
||||||
// Month not refetch or invalidated already, do it now.
|
// Month not refetch or invalidated already, do it now.
|
||||||
treatedMonths[monthId] = true;
|
treatedMonths[monthId] = true;
|
||||||
|
@ -686,7 +696,7 @@ export class AddonCalendarHelperProvider {
|
||||||
invalidateTimestarts.map((fetchTime) => {
|
invalidateTimestarts.map((fetchTime) => {
|
||||||
const day = moment(new Date(fetchTime * 1000));
|
const day = moment(new Date(fetchTime * 1000));
|
||||||
|
|
||||||
const monthId = this.getMonthId(day.year(), day.month() + 1);
|
const monthId = this.getMonthId({ year: day.year(), monthNumber: day.month() + 1 });
|
||||||
if (!treatedMonths[monthId]) {
|
if (!treatedMonths[monthId]) {
|
||||||
// Month not refetch or invalidated already, do it now.
|
// Month not refetch or invalidated already, do it now.
|
||||||
treatedMonths[monthId] = true;
|
treatedMonths[monthId] = true;
|
||||||
|
|
|
@ -17,15 +17,22 @@ import { CoreSwipeSlidesItemsManagerSource } from './slides-items-manager-source
|
||||||
/**
|
/**
|
||||||
* Items collection source data for "swipe slides".
|
* Items collection source data for "swipe slides".
|
||||||
*/
|
*/
|
||||||
export abstract class CoreSwipeSlidesDynamicItemsManagerSource<Item = unknown> extends CoreSwipeSlidesItemsManagerSource<Item> {
|
export abstract class CoreSwipeSlidesDynamicItemsManagerSource<Item extends CoreSwipeSlidesDynamicItem>
|
||||||
|
extends CoreSwipeSlidesItemsManagerSource<Item> {
|
||||||
|
|
||||||
|
// Items being loaded, to prevent loading them twice.
|
||||||
|
protected loadingItems: Record<string, boolean> = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
async load(): Promise<void> {
|
async load(currentItem?: Partial<Item> | null): Promise<void> {
|
||||||
if (this.initialItem) {
|
if (!this.initialized && this.initialItem) {
|
||||||
// Load the initial item.
|
// Load the initial item.
|
||||||
await this.loadItem(this.initialItem);
|
await this.loadItem(this.initialItem);
|
||||||
|
} else if (this.initialized && currentItem) {
|
||||||
|
// Reload current item if needed.
|
||||||
|
await this.loadItem(currentItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setInitialized();
|
this.setInitialized();
|
||||||
|
@ -64,7 +71,7 @@ export abstract class CoreSwipeSlidesDynamicItemsManagerSource<Item = unknown> e
|
||||||
* @return Promise resolved when done.
|
* @return Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
async loadItemInList(item: Partial<Item>, preload = false): Promise<void> {
|
async loadItemInList(item: Partial<Item>, preload = false): Promise<void> {
|
||||||
const preloadedItem = await this.loadItemData(item, preload);
|
const preloadedItem = await this.performLoadItemData(item, preload);
|
||||||
if (!preloadedItem) {
|
if (!preloadedItem) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -113,6 +120,70 @@ export abstract class CoreSwipeSlidesDynamicItemsManagerSource<Item = unknown> e
|
||||||
this.notifyItemsUpdated();
|
this.notifyItemsUpdated();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load or preload a certain item data.
|
||||||
|
* This helper function will check some common cases so they don't have to be replicated in all loadItemData implementations.
|
||||||
|
*
|
||||||
|
* @param item Basic data about the item to load.
|
||||||
|
* @param preload Whether to preload.
|
||||||
|
* @return Promise resolved with item. Resolve with null if already loading or item is not valid (e.g. there are no more items).
|
||||||
|
*/
|
||||||
|
protected async performLoadItemData(item: Partial<Item>, preload: boolean): Promise<Item | null> {
|
||||||
|
const itemId = this.getItemId(item);
|
||||||
|
|
||||||
|
if (!itemId || this.loadingItems[itemId]) {
|
||||||
|
// Not valid or already loading, ignore it.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingItem = this.getItem(item);
|
||||||
|
if (existingItem && ((existingItem.loaded && !existingItem.dirty) || preload)) {
|
||||||
|
// Already loaded, or preloading an already preloaded item.
|
||||||
|
return existingItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the item.
|
||||||
|
this.loadingItems[itemId] = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const itemData = await this.loadItemData(item, preload);
|
||||||
|
|
||||||
|
if (itemData && !preload) {
|
||||||
|
itemData.loaded = true;
|
||||||
|
itemData.dirty = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existingItem && itemData) {
|
||||||
|
// Update item that is already in list.
|
||||||
|
Object.assign(existingItem, itemData);
|
||||||
|
|
||||||
|
return existingItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
return itemData;
|
||||||
|
} finally {
|
||||||
|
this.loadingItems[itemId] = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark all items as dirty.
|
||||||
|
*/
|
||||||
|
markAllItemsDirty(): void {
|
||||||
|
this.getItems()?.forEach(item => {
|
||||||
|
item.dirty = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark all items as not loaded.
|
||||||
|
*/
|
||||||
|
markAllItemsUnloaded(): void {
|
||||||
|
this.getItems()?.forEach(item => {
|
||||||
|
item.loaded = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load or preload a certain item data.
|
* Load or preload a certain item data.
|
||||||
*
|
*
|
||||||
|
@ -139,3 +210,8 @@ export abstract class CoreSwipeSlidesDynamicItemsManagerSource<Item = unknown> e
|
||||||
abstract getNextItem(item: Partial<Item>): Partial<Item> | null;
|
abstract getNextItem(item: Partial<Item>): Partial<Item> | null;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type CoreSwipeSlidesDynamicItem = {
|
||||||
|
loaded?: boolean; // Whether the item has been loaded. This value can affect UI (e.g. to display a spinner).
|
||||||
|
dirty?: boolean; // Whether the item data needs to be reloaded. This value usually shouldn't affect UI.
|
||||||
|
};
|
||||||
|
|
|
@ -12,14 +12,14 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { CoreSwipeSlidesDynamicItemsManagerSource } from './slides-dynamic-items-manager-source';
|
import { CoreSwipeSlidesDynamicItem, CoreSwipeSlidesDynamicItemsManagerSource } from './slides-dynamic-items-manager-source';
|
||||||
import { CoreSwipeSlidesItemsManager } from './slides-items-manager';
|
import { CoreSwipeSlidesItemsManager } from './slides-items-manager';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper class to manage items for core-swipe-slides.
|
* Helper class to manage items for core-swipe-slides.
|
||||||
*/
|
*/
|
||||||
export class CoreSwipeSlidesDynamicItemsManager<
|
export class CoreSwipeSlidesDynamicItemsManager<
|
||||||
Item = unknown,
|
Item extends CoreSwipeSlidesDynamicItem,
|
||||||
Source extends CoreSwipeSlidesDynamicItemsManagerSource<Item> = CoreSwipeSlidesDynamicItemsManagerSource<Item>,
|
Source extends CoreSwipeSlidesDynamicItemsManagerSource<Item> = CoreSwipeSlidesDynamicItemsManagerSource<Item>,
|
||||||
> extends CoreSwipeSlidesItemsManager<Item, Source> {
|
> extends CoreSwipeSlidesItemsManager<Item, Source> {
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,10 @@
|
||||||
:host {
|
:host {
|
||||||
|
--swipe-slides-min-height: auto;
|
||||||
|
|
||||||
|
ion-slides {
|
||||||
|
min-height: var(--swipe-slides-min-height);
|
||||||
|
}
|
||||||
|
|
||||||
ion-slide {
|
ion-slide {
|
||||||
display: block;
|
display: block;
|
||||||
font-size: inherit;
|
font-size: inherit;
|
||||||
|
|
|
@ -396,16 +396,12 @@ export class CoreTimeUtilsProvider {
|
||||||
* @return Previous month and its year.
|
* @return Previous month and its year.
|
||||||
*/
|
*/
|
||||||
getPreviousMonth(month: YearAndMonth): YearAndMonth {
|
getPreviousMonth(month: YearAndMonth): YearAndMonth {
|
||||||
if (month.monthNumber === 1) {
|
const dayMoment = moment().year(month.year).month(month.monthNumber - 1);
|
||||||
return {
|
dayMoment.subtract(1, 'month');
|
||||||
monthNumber: 12,
|
|
||||||
year: month.year - 1,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
monthNumber: month.monthNumber - 1,
|
year: dayMoment.year(),
|
||||||
year: month.year,
|
monthNumber: dayMoment.month() + 1,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -416,29 +412,27 @@ export class CoreTimeUtilsProvider {
|
||||||
* @return Next month and its year.
|
* @return Next month and its year.
|
||||||
*/
|
*/
|
||||||
getNextMonth(month: YearAndMonth): YearAndMonth {
|
getNextMonth(month: YearAndMonth): YearAndMonth {
|
||||||
if (month.monthNumber === 12) {
|
const dayMoment = moment().year(month.year).month(month.monthNumber - 1);
|
||||||
return {
|
dayMoment.add(1, 'month');
|
||||||
monthNumber: 1,
|
|
||||||
year: month.year + 1,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
monthNumber: month.monthNumber + 1,
|
year: dayMoment.year(),
|
||||||
year: month.year,
|
monthNumber: dayMoment.month() + 1,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if a certain month is current month.
|
* Get current month.
|
||||||
*
|
*
|
||||||
* @param month Year and month.
|
* @return Current month.
|
||||||
* @return Whether it's current month.
|
|
||||||
*/
|
*/
|
||||||
isCurrentMonth(month: YearAndMonth): boolean {
|
getCurrentMonth(): YearAndMonth {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
|
|
||||||
return month.year == now.getFullYear() && month.monthNumber == now.getMonth() + 1;
|
return {
|
||||||
|
year: now.getFullYear(),
|
||||||
|
monthNumber: now.getMonth() + 1,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -454,8 +448,6 @@ export class CoreTimeUtilsProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if two months are the same.
|
|
||||||
*
|
|
||||||
* @param monthA Month A.
|
* @param monthA Month A.
|
||||||
* @param monthB Month B.
|
* @param monthB Month B.
|
||||||
* @return Whether it's same month.
|
* @return Whether it's same month.
|
||||||
|
@ -464,11 +456,94 @@ export class CoreTimeUtilsProvider {
|
||||||
return monthA.monthNumber === monthB.monthNumber && monthA.year === monthB.year;
|
return monthA.monthNumber === monthB.monthNumber && monthA.year === monthB.year;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a day data, return the previous day.
|
||||||
|
*
|
||||||
|
* @param day Day.
|
||||||
|
* @return Previous day.
|
||||||
|
*/
|
||||||
|
getPreviousDay(day: YearMonthDay): YearMonthDay {
|
||||||
|
const dayMoment = moment().year(day.year).month(day.monthNumber - 1).date(day.dayNumber);
|
||||||
|
dayMoment.subtract(1, 'day');
|
||||||
|
|
||||||
|
return {
|
||||||
|
year: dayMoment.year(),
|
||||||
|
monthNumber: dayMoment.month() + 1,
|
||||||
|
dayNumber: dayMoment.date(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a day data, return the next day.
|
||||||
|
*
|
||||||
|
* @param day Day.
|
||||||
|
* @return Next day.
|
||||||
|
*/
|
||||||
|
getNextDay(day: YearMonthDay): YearMonthDay {
|
||||||
|
const dayMoment = moment().year(day.year).month(day.monthNumber - 1).date(day.dayNumber);
|
||||||
|
dayMoment.add(1, 'day');
|
||||||
|
|
||||||
|
return {
|
||||||
|
year: dayMoment.year(),
|
||||||
|
monthNumber: dayMoment.month() + 1,
|
||||||
|
dayNumber: dayMoment.date(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get current day.
|
||||||
|
*
|
||||||
|
* @return Current day.
|
||||||
|
*/
|
||||||
|
getCurrentDay(): YearMonthDay {
|
||||||
|
const now = new Date();
|
||||||
|
|
||||||
|
return {
|
||||||
|
year: now.getFullYear(),
|
||||||
|
monthNumber: now.getMonth() + 1,
|
||||||
|
dayNumber: now.getDate(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a certain day is a past day.
|
||||||
|
*
|
||||||
|
* @param day Day.
|
||||||
|
* @return Whether it's a past day.
|
||||||
|
*/
|
||||||
|
isPastDay(day: YearMonthDay): boolean {
|
||||||
|
const now = new Date();
|
||||||
|
|
||||||
|
return day.year < now.getFullYear() || (day.year === now.getFullYear() && day.monthNumber < now.getMonth() + 1) ||
|
||||||
|
(day.year === now.getFullYear() && day.monthNumber === now.getMonth() + 1 && day.dayNumber < now.getDate());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if two days are the same.
|
||||||
|
*
|
||||||
|
* @param dayA Day A.
|
||||||
|
* @param dayB Day B.
|
||||||
|
* @return Whether it's same day.
|
||||||
|
*/
|
||||||
|
isSameDay(dayA: YearMonthDay, dayB: YearMonthDay): boolean {
|
||||||
|
return dayA.dayNumber === dayB.dayNumber && dayA.monthNumber === dayB.monthNumber && dayA.year === dayB.year;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CoreTimeUtils = makeSingleton(CoreTimeUtilsProvider);
|
export const CoreTimeUtils = makeSingleton(CoreTimeUtilsProvider);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data to identify a month.
|
||||||
|
*/
|
||||||
export type YearAndMonth = {
|
export type YearAndMonth = {
|
||||||
year: number; // Year number.
|
year: number; // Year number.
|
||||||
monthNumber: number; // Month number (1 to 12).
|
monthNumber: number; // Month number (1 to 12).
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data to identify a day.
|
||||||
|
*/
|
||||||
|
export type YearMonthDay = YearAndMonth & {
|
||||||
|
dayNumber: number; // Day number.
|
||||||
|
};
|
||||||
|
|
Loading…
Reference in New Issue