MOBILE-3021 calendar: Display offline events too
parent
661709acb4
commit
b202d92dc3
|
@ -19,7 +19,7 @@
|
|||
</ion-grid>
|
||||
|
||||
<!-- Calendar view. -->
|
||||
<ion-grid no-padding>
|
||||
<ion-grid padding-horizontal>
|
||||
<!-- List of days. -->
|
||||
<ion-row>
|
||||
<ion-col text-center *ngFor="let day of weekDays" class="addon-calendar-weekdays">
|
||||
|
@ -38,11 +38,15 @@
|
|||
|
||||
<!-- In tablet, display list of events. -->
|
||||
<div class="hidden-phone" class="addon-calendar-day-events">
|
||||
<p *ngFor="let event of day.filteredEvents | slice:0:3">
|
||||
<span class="calendar_event_type calendar_event_{{event.eventtype}}"></span>
|
||||
{{event.name}}
|
||||
</p>
|
||||
<p *ngIf="day.filteredEvents.length > 3">{{ 'core.nummore' | translate:{$a: day.filteredEvents.length - 3} }}</p>
|
||||
<ng-container *ngFor="let event of day.filteredEvents | slice:0:4; let index = index">
|
||||
<p *ngIf="index < 3 || day.filteredEvents.length == 4" class="addon-calendar-event" (click)="eventClicked(event)">
|
||||
<span class="calendar_event_type calendar_event_{{event.eventtype}}"></span>
|
||||
<ion-icon *ngIf="event.offline && !event.deleted" name="time"></ion-icon>
|
||||
<ion-icon *ngIf="event.deleted" name="trash"></ion-icon>
|
||||
{{event.name}}
|
||||
</p>
|
||||
</ng-container>
|
||||
<p *ngIf="day.filteredEvents.length > 4"><b>{{ 'core.nummore' | translate:{$a: day.filteredEvents.length - 3} }}</b></p>
|
||||
</div>
|
||||
</ion-col>
|
||||
<ion-col *ngFor="let value of week.postpadding" class="dayblank"></ion-col> <!-- Empty slots (last week). -->
|
||||
|
|
|
@ -13,6 +13,15 @@ ion-app.app-root addon-calendar-calendar {
|
|||
|
||||
.addon-calendar-day-events {
|
||||
@include text-align('start');
|
||||
|
||||
ion-icon {
|
||||
@include margin-horizontal(null, 2px);
|
||||
font-size: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
.addon-calendar-event {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.calendar_event_type {
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component, OnDestroy, OnInit, Input, OnChanges, SimpleChange } from '@angular/core';
|
||||
import { Component, OnDestroy, OnInit, Input, OnChanges, SimpleChange, Output, EventEmitter } from '@angular/core';
|
||||
import { CoreEventsProvider } from '@providers/events';
|
||||
import { CoreSitesProvider } from '@providers/sites';
|
||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||
|
@ -20,6 +20,7 @@ import { CoreTimeUtilsProvider } from '@providers/utils/time';
|
|||
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||
import { AddonCalendarProvider } from '../../providers/calendar';
|
||||
import { AddonCalendarHelperProvider } from '../../providers/helper';
|
||||
import { AddonCalendarOfflineProvider } from '../../providers/calendar-offline';
|
||||
import { CoreCoursesProvider } from '@core/courses/providers/courses';
|
||||
|
||||
/**
|
||||
|
@ -35,6 +36,7 @@ export class AddonCalendarCalendarComponent implements OnInit, OnChanges, OnDest
|
|||
@Input() courseId: number | string;
|
||||
@Input() categoryId: number | string; // Category ID the course belongs to.
|
||||
@Input() canNavigate?: string | boolean; // Whether to include arrows to change the month. Defaults to true.
|
||||
@Output() onEventClicked = new EventEmitter<number>();
|
||||
|
||||
periodName: string;
|
||||
weekDays: any[];
|
||||
|
@ -45,16 +47,39 @@ export class AddonCalendarCalendarComponent implements OnInit, OnChanges, OnDest
|
|||
protected month: number;
|
||||
protected categoriesRetrieved = false;
|
||||
protected categories = {};
|
||||
protected currentSiteId: string;
|
||||
protected offlineEvents: {[monthId: string]: {[day: number]: any[]}} = {}; // Offline events classified in month & day.
|
||||
protected offlineEditedEventsIds = []; // IDs of events edited in offline.
|
||||
protected deletedEvents = []; // Events deleted in offline.
|
||||
|
||||
// Observers.
|
||||
protected undeleteEventObserver: any;
|
||||
|
||||
constructor(eventsProvider: CoreEventsProvider,
|
||||
sitesProvider: CoreSitesProvider,
|
||||
private calendarProvider: AddonCalendarProvider,
|
||||
private calendarHelper: AddonCalendarHelperProvider,
|
||||
private calendarOffline: AddonCalendarOfflineProvider,
|
||||
private domUtils: CoreDomUtilsProvider,
|
||||
private timeUtils: CoreTimeUtilsProvider,
|
||||
private utils: CoreUtilsProvider,
|
||||
private coursesProvider: CoreCoursesProvider) {
|
||||
|
||||
this.currentSiteId = sitesProvider.getCurrentSiteId();
|
||||
|
||||
// Listen for events "undeleted" (offline).
|
||||
this.undeleteEventObserver = eventsProvider.on(AddonCalendarProvider.UNDELETED_EVENT_EVENT, (data) => {
|
||||
if (data && data.eventId) {
|
||||
// Mark it as undeleted, no need to refresh.
|
||||
this.undeleteEvent(data.eventId);
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}, this.currentSiteId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -76,27 +101,63 @@ export class AddonCalendarCalendarComponent implements OnInit, OnChanges, OnDest
|
|||
ngOnChanges(changes: {[name: string]: SimpleChange}): void {
|
||||
|
||||
if ((changes.courseId || changes.categoryId) && this.weeks) {
|
||||
const courseId = this.courseId ? Number(this.courseId) : undefined,
|
||||
categoryId = this.categoryId ? Number(this.categoryId) : undefined;
|
||||
|
||||
this.filterEvents(courseId, categoryId);
|
||||
this.filterEvents();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch contacts.
|
||||
*
|
||||
* @param {boolean} [refresh=false] True if we are refreshing contacts, false if we are loading more.
|
||||
* @param {boolean} [refresh=false] True if we are refreshing events.
|
||||
* @return {Promise<any>} Promise resolved when done.
|
||||
*/
|
||||
fetchData(refresh: boolean = false): Promise<any> {
|
||||
const courseId = this.courseId ? Number(this.courseId) : undefined,
|
||||
categoryId = this.categoryId ? Number(this.categoryId) : undefined,
|
||||
promises = [];
|
||||
const promises = [];
|
||||
|
||||
promises.push(this.loadCategories());
|
||||
|
||||
promises.push(this.calendarProvider.getMonthlyEvents(this.year, this.month, courseId, categoryId).then((result) => {
|
||||
// Get offline events.
|
||||
promises.push(this.calendarOffline.getAllEditedEvents().then((events) => {
|
||||
// Format data.
|
||||
events.forEach((event) => {
|
||||
event.offline = true;
|
||||
this.calendarHelper.formatEventData(event);
|
||||
});
|
||||
|
||||
// Classify them by month.
|
||||
this.offlineEvents = this.calendarHelper.classifyIntoMonths(events);
|
||||
|
||||
// Get the IDs of events edited in offline.
|
||||
const filtered = events.filter((event) => {
|
||||
return event.id > 0;
|
||||
});
|
||||
this.offlineEditedEventsIds = filtered.map((event) => {
|
||||
return event.id;
|
||||
});
|
||||
}));
|
||||
|
||||
// Get events deleted in offline.
|
||||
promises.push(this.calendarOffline.getAllDeletedEventsIds().then((ids) => {
|
||||
this.deletedEvents = ids;
|
||||
}));
|
||||
|
||||
return Promise.all(promises).then(() => {
|
||||
return this.fetchEvents();
|
||||
}).catch((error) => {
|
||||
this.domUtils.showErrorModalDefault(error, 'addon.calendar.errorloadevents', true);
|
||||
}).finally(() => {
|
||||
this.loaded = true;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the events for current month.
|
||||
*
|
||||
* @return {Promise<any>} Promise resolved when done.
|
||||
*/
|
||||
fetchEvents(): Promise<any> {
|
||||
// Don't pass courseId and categoryId, we'll filter them locally.
|
||||
return this.calendarProvider.getMonthlyEvents(this.year, this.month).then((result) => {
|
||||
|
||||
// Calculate the period name. We don't use the one in result because it's in server's language.
|
||||
this.periodName = this.timeUtils.userDate(new Date(this.year, this.month - 1).getTime(), 'core.strftimemonthyear');
|
||||
|
@ -104,13 +165,11 @@ export class AddonCalendarCalendarComponent implements OnInit, OnChanges, OnDest
|
|||
this.weekDays = this.calendarProvider.getWeekDays(result.daynames[0].dayno);
|
||||
this.weeks = result.weeks;
|
||||
|
||||
this.filterEvents(courseId, categoryId);
|
||||
}));
|
||||
// Merge the online events with offline data.
|
||||
this.mergeEvents();
|
||||
|
||||
return Promise.all(promises).catch((error) => {
|
||||
this.domUtils.showErrorModalDefault(error, 'addon.calendar.errorloadevents', true);
|
||||
}).finally(() => {
|
||||
this.loaded = true;
|
||||
// Filter events by course.
|
||||
this.filterEvents();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -140,11 +199,10 @@ export class AddonCalendarCalendarComponent implements OnInit, OnChanges, OnDest
|
|||
|
||||
/**
|
||||
* Filter events to only display events belonging to a certain course.
|
||||
*
|
||||
* @param {number} courseId Course ID.
|
||||
* @param {number} categoryId Category the course belongs to.
|
||||
*/
|
||||
filterEvents(courseId: number, categoryId: number): void {
|
||||
filterEvents(): void {
|
||||
const courseId = this.courseId ? Number(this.courseId) : undefined,
|
||||
categoryId = this.categoryId ? Number(this.categoryId) : undefined;
|
||||
|
||||
this.weeks.forEach((week) => {
|
||||
week.days.forEach((day) => {
|
||||
|
@ -165,9 +223,11 @@ export class AddonCalendarCalendarComponent implements OnInit, OnChanges, OnDest
|
|||
/**
|
||||
* Refresh events.
|
||||
*
|
||||
* @param {boolean} [sync] Whether it should try to synchronize offline events.
|
||||
* @param {boolean} [showErrors] Whether to show sync errors to the user.
|
||||
* @return {Promise<any>} Promise resolved when done.
|
||||
*/
|
||||
refreshData(): Promise<any> {
|
||||
refreshData(sync?: boolean, showErrors?: boolean): Promise<any> {
|
||||
const promises = [];
|
||||
|
||||
promises.push(this.calendarProvider.invalidateMonthlyEvents(this.year, this.month));
|
||||
|
@ -184,38 +244,145 @@ export class AddonCalendarCalendarComponent implements OnInit, OnChanges, OnDest
|
|||
* Load next month.
|
||||
*/
|
||||
loadNext(): void {
|
||||
if (this.month === 12) {
|
||||
this.month = 1;
|
||||
this.year++;
|
||||
} else {
|
||||
this.month++;
|
||||
}
|
||||
this.increaseMonth();
|
||||
|
||||
this.loaded = false;
|
||||
|
||||
this.fetchData();
|
||||
this.fetchEvents().catch((error) => {
|
||||
this.domUtils.showErrorModalDefault(error, 'addon.calendar.errorloadevents', true);
|
||||
this.decreaseMonth();
|
||||
}).finally(() => {
|
||||
this.loaded = true;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Load previous month.
|
||||
*/
|
||||
loadPrevious(): void {
|
||||
this.decreaseMonth();
|
||||
|
||||
this.loaded = false;
|
||||
|
||||
this.fetchEvents().catch((error) => {
|
||||
this.domUtils.showErrorModalDefault(error, 'addon.calendar.errorloadevents', true);
|
||||
this.increaseMonth();
|
||||
}).finally(() => {
|
||||
this.loaded = true;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* An event was clicked.
|
||||
*
|
||||
* @param {any} event Event.
|
||||
*/
|
||||
eventClicked(event: any): void {
|
||||
this.onEventClicked.emit(event.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrease the current month.
|
||||
*/
|
||||
protected decreaseMonth(): void {
|
||||
if (this.month === 1) {
|
||||
this.month = 12;
|
||||
this.year--;
|
||||
} else {
|
||||
this.month--;
|
||||
}
|
||||
}
|
||||
|
||||
this.loaded = false;
|
||||
/**
|
||||
* Increase the current month.
|
||||
*/
|
||||
protected increaseMonth(): void {
|
||||
if (this.month === 12) {
|
||||
this.month = 1;
|
||||
this.year++;
|
||||
} else {
|
||||
this.month++;
|
||||
}
|
||||
}
|
||||
|
||||
this.fetchData();
|
||||
/**
|
||||
* Merge online events with the offline events of that period.
|
||||
*/
|
||||
protected mergeEvents(): void {
|
||||
const monthOfflineEvents = this.offlineEvents[this.calendarHelper.getMonthId(this.year, this.month)];
|
||||
|
||||
if (!monthOfflineEvents && !this.deletedEvents.length) {
|
||||
// No offline events, nothing to merge.
|
||||
return;
|
||||
}
|
||||
|
||||
this.weeks.forEach((week) => {
|
||||
week.days.forEach((day) => {
|
||||
|
||||
if (this.deletedEvents.length) {
|
||||
// Mark as deleted the events that were deleted in offline.
|
||||
day.events.forEach((event) => {
|
||||
event.deleted = this.deletedEvents.indexOf(event.id) != -1;
|
||||
});
|
||||
}
|
||||
|
||||
if (this.offlineEditedEventsIds.length) {
|
||||
// Remove the online events that were modified in offline.
|
||||
day.events = day.events.filter((event) => {
|
||||
return this.offlineEditedEventsIds.indexOf(event.id) == -1;
|
||||
});
|
||||
}
|
||||
|
||||
if (monthOfflineEvents && monthOfflineEvents[day.mday]) {
|
||||
// Add the offline events (either new or edited).
|
||||
day.events = this.sortEvents(day.events.concat(monthOfflineEvents[day.mday]));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort events by timestart.
|
||||
*
|
||||
* @param {any[]} events List to sort.
|
||||
*/
|
||||
protected sortEvents(events: any[]): any[] {
|
||||
return events.sort((a, b) => {
|
||||
if (a.timestart == b.timestart) {
|
||||
return a.timeduration - b.timeduration;
|
||||
}
|
||||
|
||||
return a.timestart - b.timestart;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Undelete a certain event.
|
||||
*
|
||||
* @param {number} eventId Event ID.
|
||||
*/
|
||||
protected undeleteEvent(eventId: number): void {
|
||||
if (!this.weeks) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.weeks.forEach((week) => {
|
||||
week.days.forEach((day) => {
|
||||
const event = day.events.find((event) => {
|
||||
return event.id == eventId;
|
||||
});
|
||||
|
||||
if (event) {
|
||||
event.deleted = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Component destroyed.
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
// @todo
|
||||
this.undeleteEventObserver && this.undeleteEventObserver.off();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
</button>
|
||||
<core-context-menu>
|
||||
<core-context-menu-item [hidden]="!notificationsEnabled" [priority]="600" [content]="'core.settings.settings' | translate" (action)="openSettings()" [iconAction]="'cog'"></core-context-menu-item>
|
||||
<core-context-menu-item [hidden]="!loaded || !hasOffline || !isOnline" [priority]="400" [content]="'core.settings.synchronizenow' | translate" (action)="doRefresh(null, $event, true)" [iconAction]="syncIcon" [closeOnClick]="false"></core-context-menu-item>
|
||||
</core-context-menu>
|
||||
</ion-buttons>
|
||||
</ion-navbar>
|
||||
|
@ -16,7 +17,12 @@
|
|||
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||
</ion-refresher>
|
||||
|
||||
<addon-calendar-calendar [courseId]="courseId" [categoryId]="categoryId"></addon-calendar-calendar>
|
||||
<!-- There is data to be synchronized -->
|
||||
<ion-card class="core-warning-card" icon-start *ngIf="hasOffline">
|
||||
<ion-icon name="warning"></ion-icon> {{ 'core.hasdatatosync' | translate:{$a: 'addon.calendar.calendar' | translate} }}
|
||||
</ion-card>
|
||||
|
||||
<addon-calendar-calendar [courseId]="courseId" [categoryId]="categoryId" (onEventClicked)="gotoEvent($event)"></addon-calendar-calendar>
|
||||
|
||||
<!-- Create a calendar event. -->
|
||||
<ion-fab core-fab bottom end *ngIf="canCreate">
|
||||
|
|
|
@ -12,16 +12,22 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component, OnInit, ViewChild } from '@angular/core';
|
||||
import { Component, OnInit, OnDestroy, ViewChild, NgZone } from '@angular/core';
|
||||
import { IonicPage, NavParams, NavController, PopoverController } from 'ionic-angular';
|
||||
import { CoreAppProvider } from '@providers/app';
|
||||
import { CoreEventsProvider } from '@providers/events';
|
||||
import { CoreLocalNotificationsProvider } from '@providers/local-notifications';
|
||||
import { CoreSitesProvider } from '@providers/sites';
|
||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||
import { AddonCalendarProvider } from '../../providers/calendar';
|
||||
import { AddonCalendarOfflineProvider } from '../../providers/calendar-offline';
|
||||
import { AddonCalendarHelperProvider } from '../../providers/helper';
|
||||
import { AddonCalendarCalendarComponent } from '../../components/calendar/calendar';
|
||||
import { AddonCalendarSyncProvider } from '../../providers/calendar-sync';
|
||||
import { CoreCoursesProvider } from '@core/courses/providers/courses';
|
||||
import { CoreCoursePickerMenuPopoverComponent } from '@components/course-picker-menu/course-picker-menu-popover';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Network } from '@ionic-native/network';
|
||||
|
||||
/**
|
||||
* Page that displays the calendar events.
|
||||
|
@ -31,7 +37,7 @@ import { TranslateService } from '@ngx-translate/core';
|
|||
selector: 'page-addon-calendar-index',
|
||||
templateUrl: 'index.html',
|
||||
})
|
||||
export class AddonCalendarIndexPage implements OnInit {
|
||||
export class AddonCalendarIndexPage implements OnInit, OnDestroy {
|
||||
@ViewChild(AddonCalendarCalendarComponent) calendarComponent: AddonCalendarCalendarComponent;
|
||||
|
||||
protected allCourses = {
|
||||
|
@ -39,6 +45,18 @@ export class AddonCalendarIndexPage implements OnInit {
|
|||
fullname: this.translate.instant('core.fulllistofcourses'),
|
||||
category: -1
|
||||
};
|
||||
protected eventId: number;
|
||||
protected currentSiteId: string;
|
||||
|
||||
// Observers.
|
||||
protected newEventObserver: any;
|
||||
protected discardedObserver: any;
|
||||
protected editEventObserver: any;
|
||||
protected deleteEventObserver: any;
|
||||
protected undeleteEventObserver: any;
|
||||
protected syncObserver: any;
|
||||
protected manualSyncObserver: any;
|
||||
protected onlineObserver: any;
|
||||
|
||||
courseId: number;
|
||||
categoryId: number;
|
||||
|
@ -46,63 +64,177 @@ export class AddonCalendarIndexPage implements OnInit {
|
|||
courses: any[];
|
||||
notificationsEnabled = false;
|
||||
loaded = false;
|
||||
hasOffline = false;
|
||||
isOnline = false;
|
||||
syncIcon: string;
|
||||
|
||||
constructor(localNotificationsProvider: CoreLocalNotificationsProvider,
|
||||
navParams: NavParams,
|
||||
network: Network,
|
||||
zone: NgZone,
|
||||
sitesProvider: CoreSitesProvider,
|
||||
private navCtrl: NavController,
|
||||
private domUtils: CoreDomUtilsProvider,
|
||||
private calendarProvider: AddonCalendarProvider,
|
||||
private calendarOffline: AddonCalendarOfflineProvider,
|
||||
private calendarHelper: AddonCalendarHelperProvider,
|
||||
private calendarSync: AddonCalendarSyncProvider,
|
||||
private translate: TranslateService,
|
||||
private eventsProvider: CoreEventsProvider,
|
||||
private coursesProvider: CoreCoursesProvider,
|
||||
private popoverCtrl: PopoverController) {
|
||||
private popoverCtrl: PopoverController,
|
||||
private appProvider: CoreAppProvider) {
|
||||
|
||||
this.courseId = navParams.get('courseId');
|
||||
this.eventId = navParams.get('eventId') || false;
|
||||
this.notificationsEnabled = localNotificationsProvider.isAvailable();
|
||||
this.currentSiteId = sitesProvider.getCurrentSiteId();
|
||||
|
||||
// Listen for events added. When an event is added, reload the data.
|
||||
this.newEventObserver = eventsProvider.on(AddonCalendarProvider.NEW_EVENT_EVENT, (data) => {
|
||||
if (data && data.event) {
|
||||
this.loaded = false;
|
||||
this.refreshData(true, false);
|
||||
}
|
||||
}, this.currentSiteId);
|
||||
|
||||
// Listen for new event discarded event. When it does, reload the data.
|
||||
this.discardedObserver = eventsProvider.on(AddonCalendarProvider.NEW_EVENT_DISCARDED_EVENT, () => {
|
||||
this.loaded = false;
|
||||
this.refreshData(true, false);
|
||||
}, this.currentSiteId);
|
||||
|
||||
// Listen for events edited. When an event is edited, reload the data.
|
||||
this.editEventObserver = eventsProvider.on(AddonCalendarProvider.EDIT_EVENT_EVENT, (data) => {
|
||||
if (data && data.event) {
|
||||
this.loaded = false;
|
||||
this.refreshData(true, false);
|
||||
}
|
||||
}, this.currentSiteId);
|
||||
|
||||
// Refresh data if calendar events are synchronized automatically.
|
||||
this.syncObserver = eventsProvider.on(AddonCalendarSyncProvider.AUTO_SYNCED, (data) => {
|
||||
this.loaded = false;
|
||||
this.refreshData();
|
||||
}, this.currentSiteId);
|
||||
|
||||
// Refresh data if calendar events are synchronized manually but not by this page.
|
||||
this.manualSyncObserver = eventsProvider.on(AddonCalendarSyncProvider.MANUAL_SYNCED, (data) => {
|
||||
if (data && data.source != 'index') {
|
||||
this.loaded = false;
|
||||
this.refreshData();
|
||||
}
|
||||
}, this.currentSiteId);
|
||||
|
||||
// Update the events when an event is deleted.
|
||||
this.deleteEventObserver = eventsProvider.on(AddonCalendarProvider.DELETED_EVENT_EVENT, (data) => {
|
||||
this.loaded = false;
|
||||
this.refreshData();
|
||||
}, this.currentSiteId);
|
||||
|
||||
// Update the "hasOffline" property if an event deleted in offline is restored.
|
||||
this.undeleteEventObserver = eventsProvider.on(AddonCalendarProvider.UNDELETED_EVENT_EVENT, (data) => {
|
||||
this.calendarOffline.hasOfflineData().then((hasOffline) => {
|
||||
this.hasOffline = hasOffline;
|
||||
});
|
||||
}, this.currentSiteId);
|
||||
|
||||
// Refresh online status when changes.
|
||||
this.onlineObserver = network.onchange().subscribe(() => {
|
||||
// Execute the callback in the Angular zone, so change detection doesn't stop working.
|
||||
zone.run(() => {
|
||||
this.isOnline = this.appProvider.isOnline();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* View loaded.
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this.fetchData();
|
||||
if (this.eventId) {
|
||||
// There is an event to load, open the event in a new state.
|
||||
this.gotoEvent(this.eventId);
|
||||
}
|
||||
|
||||
this.fetchData(true, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch all the data required for the view.
|
||||
*
|
||||
* @param {boolean} [sync] Whether it should try to synchronize offline events.
|
||||
* @param {boolean} [showErrors] Whether to show sync errors to the user.
|
||||
* @return {Promise<any>} Promise resolved when done.
|
||||
*/
|
||||
fetchData(): Promise<any> {
|
||||
const promises = [];
|
||||
fetchData(sync?: boolean, showErrors?: boolean): Promise<any> {
|
||||
|
||||
// Load courses for the popover.
|
||||
promises.push(this.coursesProvider.getUserCourses(false).then((courses) => {
|
||||
// Add "All courses".
|
||||
courses.unshift(this.allCourses);
|
||||
this.courses = courses;
|
||||
this.syncIcon = 'spinner';
|
||||
this.isOnline = this.appProvider.isOnline();
|
||||
|
||||
if (this.courseId) {
|
||||
// Search the course to get the category.
|
||||
const course = this.courses.find((course) => {
|
||||
return course.id == this.courseId;
|
||||
});
|
||||
let promise;
|
||||
|
||||
if (course) {
|
||||
this.categoryId = course.category;
|
||||
if (sync) {
|
||||
// Try to synchronize offline events.
|
||||
promise = this.calendarSync.syncEvents().then((result) => {
|
||||
if (result.warnings && result.warnings.length) {
|
||||
this.domUtils.showErrorModal(result.warnings[0]);
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
// Check if user can create events.
|
||||
promises.push(this.calendarHelper.canEditEvents(this.courseId).then((canEdit) => {
|
||||
this.canCreate = canEdit;
|
||||
}));
|
||||
if (result.updated) {
|
||||
// Trigger a manual sync event.
|
||||
result.source = 'index';
|
||||
|
||||
return Promise.all(promises).catch((error) => {
|
||||
this.eventsProvider.trigger(AddonCalendarSyncProvider.MANUAL_SYNCED, result, this.currentSiteId);
|
||||
}
|
||||
}).catch((error) => {
|
||||
if (showErrors) {
|
||||
this.domUtils.showErrorModalDefault(error, 'core.errorsync', true);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
promise = Promise.resolve();
|
||||
}
|
||||
|
||||
return promise.then(() => {
|
||||
const promises = [];
|
||||
|
||||
this.hasOffline = false;
|
||||
|
||||
// Load courses for the popover.
|
||||
promises.push(this.coursesProvider.getUserCourses(false).then((courses) => {
|
||||
// Add "All courses".
|
||||
courses.unshift(this.allCourses);
|
||||
this.courses = courses;
|
||||
|
||||
if (this.courseId) {
|
||||
// Search the course to get the category.
|
||||
const course = this.courses.find((course) => {
|
||||
return course.id == this.courseId;
|
||||
});
|
||||
|
||||
if (course) {
|
||||
this.categoryId = course.category;
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
// Check if user can create events.
|
||||
promises.push(this.calendarHelper.canEditEvents(this.courseId).then((canEdit) => {
|
||||
this.canCreate = canEdit;
|
||||
}));
|
||||
|
||||
// Check if there is offline data.
|
||||
promises.push(this.calendarOffline.hasOfflineData().then((hasOffline) => {
|
||||
this.hasOffline = hasOffline;
|
||||
}));
|
||||
|
||||
return Promise.all(promises);
|
||||
}).catch((error) => {
|
||||
this.domUtils.showErrorModalDefault(error, 'addon.calendar.errorloadevents', true);
|
||||
}).finally(() => {
|
||||
this.loaded = true;
|
||||
this.syncIcon = 'sync';
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -110,13 +242,31 @@ export class AddonCalendarIndexPage implements OnInit {
|
|||
* Refresh the data.
|
||||
*
|
||||
* @param {any} [refresher] Refresher.
|
||||
* @param {Function} [done] Function to call when done.
|
||||
* @param {boolean} [showErrors] Whether to show sync errors to the user.
|
||||
* @return {Promise<any>} Promise resolved when done.
|
||||
*/
|
||||
doRefresh(refresher?: any): void {
|
||||
if (!this.loaded) {
|
||||
return;
|
||||
doRefresh(refresher?: any, done?: () => void, showErrors?: boolean): Promise<any> {
|
||||
if (this.loaded) {
|
||||
return this.refreshData(true, showErrors).finally(() => {
|
||||
refresher && refresher.complete();
|
||||
done && done();
|
||||
});
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the data.
|
||||
*
|
||||
* @param {boolean} [sync] Whether it should try to synchronize offline events.
|
||||
* @param {boolean} [showErrors] Whether to show sync errors to the user.
|
||||
* @return {Promise<any>} Promise resolved when done.
|
||||
*/
|
||||
refreshData(sync?: boolean, showErrors?: boolean): Promise<any> {
|
||||
this.syncIcon = 'spinner';
|
||||
|
||||
const promises = [];
|
||||
|
||||
promises.push(this.calendarProvider.invalidateAllowedEventTypes().then(() => {
|
||||
|
@ -126,11 +276,27 @@ export class AddonCalendarIndexPage implements OnInit {
|
|||
// Refresh the sub-component.
|
||||
promises.push(this.calendarComponent.refreshData());
|
||||
|
||||
Promise.all(promises).finally(() => {
|
||||
refresher && refresher.complete();
|
||||
return Promise.all(promises).finally(() => {
|
||||
return this.fetchData(sync, showErrors);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to a particular event.
|
||||
*
|
||||
* @param {number} eventId Event to load.
|
||||
*/
|
||||
gotoEvent(eventId: number): void {
|
||||
if (eventId < 0) {
|
||||
// It's an offline event, go to the edit page.
|
||||
this.openEdit(eventId);
|
||||
} else {
|
||||
this.navCtrl.push('AddonCalendarEventPage', {
|
||||
id: eventId
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the context menu.
|
||||
*
|
||||
|
@ -182,4 +348,18 @@ export class AddonCalendarIndexPage implements OnInit {
|
|||
openSettings(): void {
|
||||
this.navCtrl.push('AddonCalendarSettingsPage');
|
||||
}
|
||||
|
||||
/**
|
||||
* Page destroyed.
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.newEventObserver && this.newEventObserver.off();
|
||||
this.discardedObserver && this.discardedObserver.off();
|
||||
this.editEventObserver && this.editEventObserver.off();
|
||||
this.deleteEventObserver && this.deleteEventObserver.off();
|
||||
this.undeleteEventObserver && this.undeleteEventObserver.off();
|
||||
this.syncObserver && this.syncObserver.off();
|
||||
this.manualSyncObserver && this.manualSyncObserver.off();
|
||||
this.onlineObserver && this.onlineObserver.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -280,6 +280,18 @@ export class AddonCalendarOfflineProvider {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether there's offline data for a site.
|
||||
*
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<boolean>} Promise resolved with boolean: true if has offline data, false otherwise.
|
||||
*/
|
||||
hasOfflineData(siteId?: string): Promise<boolean> {
|
||||
return this.getAllEventsIds(siteId).then((ids) => {
|
||||
return ids.length > 0;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an event is deleted.
|
||||
*
|
||||
|
|
|
@ -18,6 +18,7 @@ import { CoreSitesProvider } from '@providers/sites';
|
|||
import { CoreCourseProvider } from '@core/course/providers/course';
|
||||
import { AddonCalendarProvider } from './calendar';
|
||||
import { CoreConstants } from '@core/constants';
|
||||
import * as moment from 'moment';
|
||||
|
||||
/**
|
||||
* Service that provides some features regarding lists of courses and categories.
|
||||
|
@ -85,6 +86,41 @@ export class AddonCalendarHelperProvider {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Classify events into their respective months and days. If an event duration covers more than one day,
|
||||
* it will be included in all the days it lasts.
|
||||
*
|
||||
* @param {any[]} events Events to classify.
|
||||
* @return {{[monthId: string]: {[day: number]: any[]}}} Object with the classified events.
|
||||
*/
|
||||
classifyIntoMonths(events: any[]): {[monthId: string]: {[day: number]: any[]}} {
|
||||
|
||||
const result = {};
|
||||
|
||||
events.forEach((event) => {
|
||||
const treatedDay = moment(new Date(event.timestart * 1000)),
|
||||
endDay = moment(new Date((event.timestart + (event.timeduration || 0)) * 1000));
|
||||
|
||||
// Add the event to all the days it lasts.
|
||||
while (!treatedDay.isAfter(endDay, 'day')) {
|
||||
const monthId = this.getMonthId(treatedDay.year(), treatedDay.month() + 1),
|
||||
day = treatedDay.date();
|
||||
|
||||
if (!result[monthId]) {
|
||||
result[monthId] = {};
|
||||
}
|
||||
if (!result[monthId][day]) {
|
||||
result[monthId][day] = [];
|
||||
}
|
||||
result[monthId][day].push(event);
|
||||
|
||||
treatedDay.add(1, 'day'); // Treat next day.
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience function to format some event data to be rendered.
|
||||
*
|
||||
|
@ -97,7 +133,7 @@ export class AddonCalendarHelperProvider {
|
|||
e.moduleIcon = e.icon;
|
||||
}
|
||||
|
||||
if (e.id < 0) {
|
||||
if (typeof e.duration != 'undefined') {
|
||||
// It's an offline event, add some calculated data.
|
||||
e.format = 1;
|
||||
e.visible = 1;
|
||||
|
@ -140,6 +176,17 @@ export class AddonCalendarHelperProvider {
|
|||
return options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the month "id" (year + month).
|
||||
*
|
||||
* @param {number} year Year.
|
||||
* @param {number} month Month.
|
||||
* @return {string} The "id".
|
||||
*/
|
||||
getMonthId(year: number, month: number): string {
|
||||
return year + '#' + month;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the data of an event has changed.
|
||||
*
|
||||
|
|
Loading…
Reference in New Issue