commit
370dc7564c
|
@ -30,6 +30,7 @@
|
||||||
"addon.block_badges.pluginname": "block_badges",
|
"addon.block_badges.pluginname": "block_badges",
|
||||||
"addon.block_blogmenu.pluginname": "block_blog_menu",
|
"addon.block_blogmenu.pluginname": "block_blog_menu",
|
||||||
"addon.block_blogrecent.nocourses": "block_blog_recent",
|
"addon.block_blogrecent.nocourses": "block_blog_recent",
|
||||||
|
"addon.block_blogrecent.pluginname": "block_blog_recent",
|
||||||
"addon.block_blogtags.pluginname": "block_blog_tags",
|
"addon.block_blogtags.pluginname": "block_blog_tags",
|
||||||
"addon.block_calendarmonth.pluginname": "block_calendar_month",
|
"addon.block_calendarmonth.pluginname": "block_calendar_month",
|
||||||
"addon.block_calendarupcoming.pluginname": "block_calendar_upcoming",
|
"addon.block_calendarupcoming.pluginname": "block_calendar_upcoming",
|
||||||
|
@ -51,12 +52,12 @@
|
||||||
"addon.block_newsitems.pluginname": "block_news_items",
|
"addon.block_newsitems.pluginname": "block_news_items",
|
||||||
"addon.block_onlineusers.pluginname": "block_online_users",
|
"addon.block_onlineusers.pluginname": "block_online_users",
|
||||||
"addon.block_privatefiles.pluginname": "block_private_files",
|
"addon.block_privatefiles.pluginname": "block_private_files",
|
||||||
|
"addon.block_recentactivity.pluginname": "block_recent_activity",
|
||||||
"addon.block_recentlyaccessedcourses.nocourses": "block_recentlyaccessedcourses",
|
"addon.block_recentlyaccessedcourses.nocourses": "block_recentlyaccessedcourses",
|
||||||
"addon.block_recentlyaccessedcourses.pluginname": "block_recentlyaccessedcourses",
|
"addon.block_recentlyaccessedcourses.pluginname": "block_recentlyaccessedcourses",
|
||||||
"addon.block_recentlyaccesseditems.noitems": "block_recentlyaccesseditems",
|
"addon.block_recentlyaccesseditems.noitems": "block_recentlyaccesseditems",
|
||||||
"addon.block_recentactivity.pluginname": "block_recent_activity",
|
"addon.block_recentlyaccesseditems.pluginname": "block_recentlyaccesseditems",
|
||||||
"addon.block_rssclient.pluginname": "block_rss_client",
|
"addon.block_rssclient.pluginname": "block_rss_client",
|
||||||
"addon.block_glossaryrandom.pluginname": "block_glossary_random",
|
|
||||||
"addon.block_selfcompletion.pluginname": "block_selfcompletion",
|
"addon.block_selfcompletion.pluginname": "block_selfcompletion",
|
||||||
"addon.block_sitemainmenu.pluginname": "block_site_main_menu",
|
"addon.block_sitemainmenu.pluginname": "block_site_main_menu",
|
||||||
"addon.block_starredcourses.nocourses": "block_starredcourses",
|
"addon.block_starredcourses.nocourses": "block_starredcourses",
|
||||||
|
@ -83,12 +84,15 @@
|
||||||
"addon.blog.publishtoworld": "blog",
|
"addon.blog.publishtoworld": "blog",
|
||||||
"addon.blog.showonlyyourentries": "local_moodlemobileapp",
|
"addon.blog.showonlyyourentries": "local_moodlemobileapp",
|
||||||
"addon.blog.siteblogheading": "blog",
|
"addon.blog.siteblogheading": "blog",
|
||||||
|
"addon.calendar.allday": "calendar",
|
||||||
"addon.calendar.calendar": "calendar",
|
"addon.calendar.calendar": "calendar",
|
||||||
"addon.calendar.calendarevent": "local_moodlemobileapp",
|
"addon.calendar.calendarevent": "local_moodlemobileapp",
|
||||||
"addon.calendar.calendarevents": "local_moodlemobileapp",
|
"addon.calendar.calendarevents": "local_moodlemobileapp",
|
||||||
"addon.calendar.calendarreminders": "local_moodlemobileapp",
|
"addon.calendar.calendarreminders": "local_moodlemobileapp",
|
||||||
"addon.calendar.confirmeventdelete": "calendar",
|
"addon.calendar.confirmeventdelete": "calendar",
|
||||||
"addon.calendar.confirmeventseriesdelete": "calendar",
|
"addon.calendar.confirmeventseriesdelete": "calendar",
|
||||||
|
"addon.calendar.daynext": "calendar",
|
||||||
|
"addon.calendar.dayprev": "calendar",
|
||||||
"addon.calendar.defaultnotificationtime": "local_moodlemobileapp",
|
"addon.calendar.defaultnotificationtime": "local_moodlemobileapp",
|
||||||
"addon.calendar.deleteallevents": "calendar",
|
"addon.calendar.deleteallevents": "calendar",
|
||||||
"addon.calendar.deleteevent": "calendar",
|
"addon.calendar.deleteevent": "calendar",
|
||||||
|
@ -106,9 +110,14 @@
|
||||||
"addon.calendar.eventname": "calendar",
|
"addon.calendar.eventname": "calendar",
|
||||||
"addon.calendar.eventstarttime": "calendar",
|
"addon.calendar.eventstarttime": "calendar",
|
||||||
"addon.calendar.eventtype": "calendar",
|
"addon.calendar.eventtype": "calendar",
|
||||||
|
"addon.calendar.fri": "calendar",
|
||||||
|
"addon.calendar.friday": "calendar",
|
||||||
"addon.calendar.gotoactivity": "calendar",
|
"addon.calendar.gotoactivity": "calendar",
|
||||||
"addon.calendar.invalidtimedurationminutes": "calendar",
|
"addon.calendar.invalidtimedurationminutes": "calendar",
|
||||||
"addon.calendar.invalidtimedurationuntil": "calendar",
|
"addon.calendar.invalidtimedurationuntil": "calendar",
|
||||||
|
"addon.calendar.mon": "calendar",
|
||||||
|
"addon.calendar.monday": "calendar",
|
||||||
|
"addon.calendar.monthlyview": "calendar",
|
||||||
"addon.calendar.newevent": "calendar",
|
"addon.calendar.newevent": "calendar",
|
||||||
"addon.calendar.noevents": "local_moodlemobileapp",
|
"addon.calendar.noevents": "local_moodlemobileapp",
|
||||||
"addon.calendar.nopermissiontoupdatecalendar": "error",
|
"addon.calendar.nopermissiontoupdatecalendar": "error",
|
||||||
|
@ -118,7 +127,17 @@
|
||||||
"addon.calendar.repeateditthis": "calendar",
|
"addon.calendar.repeateditthis": "calendar",
|
||||||
"addon.calendar.repeatevent": "calendar",
|
"addon.calendar.repeatevent": "calendar",
|
||||||
"addon.calendar.repeatweeksl": "calendar",
|
"addon.calendar.repeatweeksl": "calendar",
|
||||||
|
"addon.calendar.sat": "calendar",
|
||||||
|
"addon.calendar.saturday": "calendar",
|
||||||
"addon.calendar.setnewreminder": "local_moodlemobileapp",
|
"addon.calendar.setnewreminder": "local_moodlemobileapp",
|
||||||
|
"addon.calendar.sun": "calendar",
|
||||||
|
"addon.calendar.sunday": "calendar",
|
||||||
|
"addon.calendar.thu": "calendar",
|
||||||
|
"addon.calendar.thursday": "calendar",
|
||||||
|
"addon.calendar.today": "calendar",
|
||||||
|
"addon.calendar.tomorrow": "calendar",
|
||||||
|
"addon.calendar.tue": "calendar",
|
||||||
|
"addon.calendar.tuesday": "calendar",
|
||||||
"addon.calendar.typecategory": "calendar",
|
"addon.calendar.typecategory": "calendar",
|
||||||
"addon.calendar.typeclose": "calendar",
|
"addon.calendar.typeclose": "calendar",
|
||||||
"addon.calendar.typecourse": "calendar",
|
"addon.calendar.typecourse": "calendar",
|
||||||
|
@ -128,6 +147,11 @@
|
||||||
"addon.calendar.typeopen": "calendar",
|
"addon.calendar.typeopen": "calendar",
|
||||||
"addon.calendar.typesite": "calendar",
|
"addon.calendar.typesite": "calendar",
|
||||||
"addon.calendar.typeuser": "calendar",
|
"addon.calendar.typeuser": "calendar",
|
||||||
|
"addon.calendar.upcomingevents": "calendar",
|
||||||
|
"addon.calendar.wed": "calendar",
|
||||||
|
"addon.calendar.wednesday": "calendar",
|
||||||
|
"addon.calendar.when": "calendar",
|
||||||
|
"addon.calendar.yesterday": "calendar",
|
||||||
"addon.competency.activities": "tool_lp",
|
"addon.competency.activities": "tool_lp",
|
||||||
"addon.competency.competencies": "competency",
|
"addon.competency.competencies": "competency",
|
||||||
"addon.competency.competenciesmostoftennotproficientincourse": "tool_lp",
|
"addon.competency.competenciesmostoftennotproficientincourse": "tool_lp",
|
||||||
|
@ -1663,6 +1687,7 @@
|
||||||
"core.notingroup": "moodle",
|
"core.notingroup": "moodle",
|
||||||
"core.notsent": "local_moodlemobileapp",
|
"core.notsent": "local_moodlemobileapp",
|
||||||
"core.now": "moodle",
|
"core.now": "moodle",
|
||||||
|
"core.nummore": "local_moodlemobileapp",
|
||||||
"core.numwords": "moodle",
|
"core.numwords": "moodle",
|
||||||
"core.offline": "message",
|
"core.offline": "message",
|
||||||
"core.ok": "moodle",
|
"core.ok": "moodle",
|
||||||
|
|
|
@ -16,6 +16,7 @@ import { Injectable, Injector } from '@angular/core';
|
||||||
import { CoreBlockHandlerData } from '@core/block/providers/delegate';
|
import { CoreBlockHandlerData } from '@core/block/providers/delegate';
|
||||||
import { CoreBlockOnlyTitleComponent } from '@core/block/components/only-title-block/only-title-block';
|
import { CoreBlockOnlyTitleComponent } from '@core/block/components/only-title-block/only-title-block';
|
||||||
import { CoreBlockBaseHandler } from '@core/block/classes/base-block-handler';
|
import { CoreBlockBaseHandler } from '@core/block/classes/base-block-handler';
|
||||||
|
import { AddonCalendarProvider } from '@addon/calendar/providers/calendar';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Block handler.
|
* Block handler.
|
||||||
|
@ -25,7 +26,7 @@ export class AddonBlockCalendarMonthHandler extends CoreBlockBaseHandler {
|
||||||
name = 'AddonBlockCalendarMonth';
|
name = 'AddonBlockCalendarMonth';
|
||||||
blockName = 'calendar_month';
|
blockName = 'calendar_month';
|
||||||
|
|
||||||
constructor() {
|
constructor(private calendarProvider: AddonCalendarProvider) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,12 +42,19 @@ export class AddonBlockCalendarMonthHandler extends CoreBlockBaseHandler {
|
||||||
getDisplayData(injector: Injector, block: any, contextLevel: string, instanceId: number)
|
getDisplayData(injector: Injector, block: any, contextLevel: string, instanceId: number)
|
||||||
: CoreBlockHandlerData | Promise<CoreBlockHandlerData> {
|
: CoreBlockHandlerData | Promise<CoreBlockHandlerData> {
|
||||||
|
|
||||||
|
let link = 'AddonCalendarListPage';
|
||||||
|
const linkParams: any = contextLevel == 'course' ? { courseId: instanceId } : {};
|
||||||
|
|
||||||
|
if (this.calendarProvider.canViewMonthInSite()) {
|
||||||
|
link = 'AddonCalendarIndexPage';
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title: 'addon.block_calendarmonth.pluginname',
|
title: 'addon.block_calendarmonth.pluginname',
|
||||||
class: 'addon-block-calendar-month',
|
class: 'addon-block-calendar-month',
|
||||||
component: CoreBlockOnlyTitleComponent,
|
component: CoreBlockOnlyTitleComponent,
|
||||||
link: 'AddonCalendarListPage',
|
link: link,
|
||||||
linkParams: contextLevel == 'course' ? { courseId: instanceId } : null
|
linkParams: linkParams
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ import { Injectable, Injector } from '@angular/core';
|
||||||
import { CoreBlockHandlerData } from '@core/block/providers/delegate';
|
import { CoreBlockHandlerData } from '@core/block/providers/delegate';
|
||||||
import { CoreBlockOnlyTitleComponent } from '@core/block/components/only-title-block/only-title-block';
|
import { CoreBlockOnlyTitleComponent } from '@core/block/components/only-title-block/only-title-block';
|
||||||
import { CoreBlockBaseHandler } from '@core/block/classes/base-block-handler';
|
import { CoreBlockBaseHandler } from '@core/block/classes/base-block-handler';
|
||||||
|
import { AddonCalendarProvider } from '@addon/calendar/providers/calendar';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Block handler.
|
* Block handler.
|
||||||
|
@ -25,7 +26,7 @@ export class AddonBlockCalendarUpcomingHandler extends CoreBlockBaseHandler {
|
||||||
name = 'AddonBlockCalendarUpcoming';
|
name = 'AddonBlockCalendarUpcoming';
|
||||||
blockName = 'calendar_upcoming';
|
blockName = 'calendar_upcoming';
|
||||||
|
|
||||||
constructor() {
|
constructor(private calendarProvider: AddonCalendarProvider) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,12 +42,20 @@ export class AddonBlockCalendarUpcomingHandler extends CoreBlockBaseHandler {
|
||||||
getDisplayData(injector: Injector, block: any, contextLevel: string, instanceId: number)
|
getDisplayData(injector: Injector, block: any, contextLevel: string, instanceId: number)
|
||||||
: CoreBlockHandlerData | Promise<CoreBlockHandlerData> {
|
: CoreBlockHandlerData | Promise<CoreBlockHandlerData> {
|
||||||
|
|
||||||
|
let link = 'AddonCalendarListPage';
|
||||||
|
const linkParams: any = contextLevel == 'course' ? { courseId: instanceId } : {};
|
||||||
|
|
||||||
|
if (this.calendarProvider.canViewMonthInSite()) {
|
||||||
|
link = 'AddonCalendarIndexPage';
|
||||||
|
linkParams.upcoming = true;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title: 'addon.block_calendarupcoming.pluginname',
|
title: 'addon.block_calendarupcoming.pluginname',
|
||||||
class: 'addon-block-calendar-upcoming',
|
class: 'addon-block-calendar-upcoming',
|
||||||
component: CoreBlockOnlyTitleComponent,
|
component: CoreBlockOnlyTitleComponent,
|
||||||
link: 'AddonCalendarListPage',
|
link: link,
|
||||||
linkParams: contextLevel == 'course' ? { courseId: instanceId } : null
|
linkParams: linkParams
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,12 +19,14 @@ import { AddonCalendarHelperProvider } from './providers/helper';
|
||||||
import { AddonCalendarSyncProvider } from './providers/calendar-sync';
|
import { AddonCalendarSyncProvider } from './providers/calendar-sync';
|
||||||
import { AddonCalendarMainMenuHandler } from './providers/mainmenu-handler';
|
import { AddonCalendarMainMenuHandler } from './providers/mainmenu-handler';
|
||||||
import { AddonCalendarSyncCronHandler } from './providers/sync-cron-handler';
|
import { AddonCalendarSyncCronHandler } from './providers/sync-cron-handler';
|
||||||
|
import { AddonCalendarViewLinkHandler } from './providers/view-link-handler';
|
||||||
import { CoreMainMenuDelegate } from '@core/mainmenu/providers/delegate';
|
import { CoreMainMenuDelegate } from '@core/mainmenu/providers/delegate';
|
||||||
import { CoreCronDelegate } from '@providers/cron';
|
import { CoreCronDelegate } from '@providers/cron';
|
||||||
import { CoreInitDelegate } from '@providers/init';
|
import { CoreInitDelegate } from '@providers/init';
|
||||||
import { CoreLocalNotificationsProvider } from '@providers/local-notifications';
|
import { CoreLocalNotificationsProvider } from '@providers/local-notifications';
|
||||||
import { CoreLoginHelperProvider } from '@core/login/providers/helper';
|
import { CoreLoginHelperProvider } from '@core/login/providers/helper';
|
||||||
import { CoreUpdateManagerProvider } from '@providers/update-manager';
|
import { CoreUpdateManagerProvider } from '@providers/update-manager';
|
||||||
|
import { CoreContentLinksDelegate } from '@core/contentlinks/providers/delegate';
|
||||||
|
|
||||||
// List of providers (without handlers).
|
// List of providers (without handlers).
|
||||||
export const ADDON_CALENDAR_PROVIDERS: any[] = [
|
export const ADDON_CALENDAR_PROVIDERS: any[] = [
|
||||||
|
@ -45,17 +47,20 @@ export const ADDON_CALENDAR_PROVIDERS: any[] = [
|
||||||
AddonCalendarHelperProvider,
|
AddonCalendarHelperProvider,
|
||||||
AddonCalendarSyncProvider,
|
AddonCalendarSyncProvider,
|
||||||
AddonCalendarMainMenuHandler,
|
AddonCalendarMainMenuHandler,
|
||||||
AddonCalendarSyncCronHandler
|
AddonCalendarSyncCronHandler,
|
||||||
|
AddonCalendarViewLinkHandler
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class AddonCalendarModule {
|
export class AddonCalendarModule {
|
||||||
constructor(mainMenuDelegate: CoreMainMenuDelegate, calendarHandler: AddonCalendarMainMenuHandler,
|
constructor(mainMenuDelegate: CoreMainMenuDelegate, calendarHandler: AddonCalendarMainMenuHandler,
|
||||||
initDelegate: CoreInitDelegate, calendarProvider: AddonCalendarProvider, loginHelper: CoreLoginHelperProvider,
|
initDelegate: CoreInitDelegate, calendarProvider: AddonCalendarProvider, loginHelper: CoreLoginHelperProvider,
|
||||||
localNotificationsProvider: CoreLocalNotificationsProvider, updateManager: CoreUpdateManagerProvider,
|
localNotificationsProvider: CoreLocalNotificationsProvider, updateManager: CoreUpdateManagerProvider,
|
||||||
cronDelegate: CoreCronDelegate, syncHandler: AddonCalendarSyncCronHandler) {
|
cronDelegate: CoreCronDelegate, syncHandler: AddonCalendarSyncCronHandler,
|
||||||
|
contentLinksDelegate: CoreContentLinksDelegate, viewLinkHandler: AddonCalendarViewLinkHandler) {
|
||||||
|
|
||||||
mainMenuDelegate.registerHandler(calendarHandler);
|
mainMenuDelegate.registerHandler(calendarHandler);
|
||||||
cronDelegate.register(syncHandler);
|
cronDelegate.register(syncHandler);
|
||||||
|
contentLinksDelegate.registerHandler(viewLinkHandler);
|
||||||
|
|
||||||
initDelegate.ready().then(() => {
|
initDelegate.ready().then(() => {
|
||||||
calendarProvider.scheduleAllSitesEventsNotifications();
|
calendarProvider.scheduleAllSitesEventsNotifications();
|
||||||
|
@ -70,7 +75,13 @@ export class AddonCalendarModule {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
loginHelper.redirect('AddonCalendarListPage', {eventId: data.eventid}, data.siteId);
|
// Check which page we should load.
|
||||||
|
calendarProvider.canViewMonth(data.siteId).then((canView) => {
|
||||||
|
const pageName = canView ? 'AddonCalendarIndexPage' : 'AddonCalendarListPage';
|
||||||
|
|
||||||
|
loginHelper.redirect(pageName, {eventId: data.eventid}, data.siteId);
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
|
||||||
|
<!-- Add buttons to the nav bar. -->
|
||||||
|
<core-navbar-buttons end>
|
||||||
|
<button [hidden]="!canNavigate || isCurrentMonth || !displayNavButtons" ion-button icon-only clear (click)="goToCurrentMonth()">
|
||||||
|
<core-icon name="fa-calendar-times-o"></core-icon>
|
||||||
|
</button>
|
||||||
|
</core-navbar-buttons>
|
||||||
|
|
||||||
|
<core-loading [hideUntil]="loaded" class="core-loading-center">
|
||||||
|
<!-- Period name and arrows to navigate. -->
|
||||||
|
<ion-grid padding-top>
|
||||||
|
<ion-row>
|
||||||
|
<ion-col text-start *ngIf="canNavigate">
|
||||||
|
<a ion-button icon-only clear (click)="loadPrevious()" [title]="'core.previous' | translate">
|
||||||
|
<ion-icon name="arrow-back" md="ios-arrow-back"></ion-icon>
|
||||||
|
</a>
|
||||||
|
</ion-col>
|
||||||
|
<ion-col text-center class="addon-calendar-period">
|
||||||
|
<h3>{{ periodName }}</h3>
|
||||||
|
</ion-col>
|
||||||
|
<ion-col text-end *ngIf="canNavigate">
|
||||||
|
<a ion-button icon-only clear (click)="loadNext()" [title]="'core.next' | translate">
|
||||||
|
<ion-icon name="arrow-forward" md="ios-arrow-forward"></ion-icon>
|
||||||
|
</a>
|
||||||
|
</ion-col>
|
||||||
|
</ion-row>
|
||||||
|
</ion-grid>
|
||||||
|
|
||||||
|
<!-- Calendar view. -->
|
||||||
|
<ion-grid class="addon-calendar-months">
|
||||||
|
<!-- List of days. -->
|
||||||
|
<ion-row>
|
||||||
|
<ion-col text-center *ngFor="let day of weekDays" class="addon-calendar-weekday">
|
||||||
|
<p>{{ day.shortname | translate }}</p>
|
||||||
|
</ion-col>
|
||||||
|
</ion-row>
|
||||||
|
|
||||||
|
<!-- Weeks. -->
|
||||||
|
<ion-row *ngFor="let week of weeks" class="addon-calendar-week">
|
||||||
|
<ion-col *ngFor="let value of week.prepadding" class="dayblank addon-calendar-day"></ion-col> <!-- Empty slots (first week). -->
|
||||||
|
<ion-col text-center *ngFor="let day of week.days" [ngClass]='{"hasevents": day.hasevents, "today": day.istoday, "weekend": day.isweekend, "duration_finish": day.haslastdayofevent}' class="addon-calendar-day">
|
||||||
|
<p class="addon-calendar-day-number" (click)="dayClicked(day.mday)">{{ day.mday }}</p>
|
||||||
|
|
||||||
|
<!-- In phone, display some dots to indicate the type of events. -->
|
||||||
|
<p class="hidden-tablet addon-calendar-dot-types"><span *ngFor="let type of day.calendareventtypes" class="calendar_event_type calendar_event_{{type}}"></span></p>
|
||||||
|
|
||||||
|
<!-- In tablet, display list of events. -->
|
||||||
|
<div class="hidden-phone addon-calendar-day-events">
|
||||||
|
<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.formattedType}}"></span>
|
||||||
|
<ion-icon *ngIf="event.offline && !event.deleted" name="time"></ion-icon>
|
||||||
|
<ion-icon *ngIf="event.deleted" name="trash"></ion-icon>
|
||||||
|
<img *ngIf="event.moduleIcon" src="{{event.moduleIcon}}" alt="" role="presentation" class="core-module-icon">
|
||||||
|
<span class="addon-calendar-event-time">{{ event.timestart * 1000 | coreFormatDate: timeFormat }}</span>
|
||||||
|
<span class="addon-calendar-event-name">{{event.name}}</span>
|
||||||
|
</p>
|
||||||
|
</ng-container>
|
||||||
|
<p *ngIf="day.filteredEvents.length > 4" class="addon-calendar-day-more" (click)="dayClicked(day.mday)"><b>{{ 'core.nummore' | translate:{$a: day.filteredEvents.length - 3} }}</b></p>
|
||||||
|
</div>
|
||||||
|
</ion-col>
|
||||||
|
<ion-col *ngFor="let value of week.postpadding" class="dayblank addon-calendar-day"></ion-col> <!-- Empty slots (last week). -->
|
||||||
|
</ion-row>
|
||||||
|
</ion-grid>
|
||||||
|
|
||||||
|
</core-loading>
|
|
@ -0,0 +1,130 @@
|
||||||
|
|
||||||
|
$calendar-event-category-color: $purple !default; // Purple.
|
||||||
|
$calendar-event-course-color: $red !default; // Red.
|
||||||
|
$calendar-event-group-color: $yellow !default; // Yellow.
|
||||||
|
$calendar-event-user-color: $blue !default; // Blue.
|
||||||
|
$calendar-event-site-color: $green !default; // Green.
|
||||||
|
$calendar-today-bgcolor: $core-color !default;
|
||||||
|
$calendar-today-color: $white !default;
|
||||||
|
$calendar-border-color: $gray !default;
|
||||||
|
|
||||||
|
ion-app.app-root addon-calendar-calendar {
|
||||||
|
|
||||||
|
.addon-calendar-months {
|
||||||
|
background-color: white;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.addon-calendar-day {
|
||||||
|
border-bottom: 1px solid $calendar-border-color;
|
||||||
|
@include border-end(1px, solid, $calendar-border-color);
|
||||||
|
overflow: hidden;
|
||||||
|
min-height: 70px;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
@include padding(null, null, null, 10px);
|
||||||
|
}
|
||||||
|
&:last-child {
|
||||||
|
@include border-end(0, null, null);
|
||||||
|
@include padding(null, 8px, null, null);
|
||||||
|
}
|
||||||
|
.addon-calendar-day-number {
|
||||||
|
height: 24px;
|
||||||
|
line-height: 24px;
|
||||||
|
width: max-content;
|
||||||
|
min-width: 24px;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: 500;
|
||||||
|
display: inline-block;
|
||||||
|
margin: 3px;
|
||||||
|
}
|
||||||
|
&.today .addon-calendar-day-number {
|
||||||
|
background-color: $calendar-today-bgcolor;
|
||||||
|
color: $calendar-today-color;
|
||||||
|
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
&.dayblank {
|
||||||
|
background-color: $gray-lighter;
|
||||||
|
}
|
||||||
|
|
||||||
|
.addon-calendar-event {
|
||||||
|
margin-top: 0.6em;
|
||||||
|
margin-bottom: 0.6em;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
.addon-calendar-event-name {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.addon-calendar-day-more {
|
||||||
|
@include margin(0.6em, null, 0.6em, 4px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.addon-calendar-dot-types {
|
||||||
|
@include margin(0.6em, null, 0.6em, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.addon-calendar-period {
|
||||||
|
flex-grow: 3;
|
||||||
|
h3 {
|
||||||
|
margin-top: 10px;
|
||||||
|
font-size: 1.6rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.addon-calendar-weekday {
|
||||||
|
color: $gray-dark;
|
||||||
|
border-bottom: 1px solid $list-md-border-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.addon-calendar-day-events {
|
||||||
|
@include text-align('start');
|
||||||
|
|
||||||
|
ion-icon {
|
||||||
|
@include margin-horizontal(null, 2px);
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.addon-calendar-event, .addon-calendar-day-number, .addon-calendar-day-more {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar_event_type {
|
||||||
|
display: inline-block;
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 1px solid white;
|
||||||
|
@include margin-horizontal(1px, 1px);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
&.calendar_event_category {
|
||||||
|
background-color: $calendar-event-category-color;
|
||||||
|
}
|
||||||
|
&.calendar_event_course {
|
||||||
|
background-color: $calendar-event-course-color;
|
||||||
|
}
|
||||||
|
&.calendar_event_group {
|
||||||
|
background-color: $calendar-event-group-color;
|
||||||
|
}
|
||||||
|
&.calendar_event_user {
|
||||||
|
background-color: $calendar-event-user-color;
|
||||||
|
}
|
||||||
|
&.calendar_event_site {
|
||||||
|
background-color: $calendar-event-site-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.core-module-icon {
|
||||||
|
@include margin-horizontal(1px, 1px);
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,466 @@
|
||||||
|
// (C) Copyright 2015 Martin Dougiamas
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { Component, OnDestroy, OnInit, Input, OnChanges, SimpleChange, Output, EventEmitter } from '@angular/core';
|
||||||
|
import { CoreEventsProvider } from '@providers/events';
|
||||||
|
import { CoreLocalNotificationsProvider } from '@providers/local-notifications';
|
||||||
|
import { CoreSitesProvider } from '@providers/sites';
|
||||||
|
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||||
|
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';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component that displays a calendar.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'addon-calendar-calendar',
|
||||||
|
templateUrl: 'addon-calendar-calendar.html',
|
||||||
|
})
|
||||||
|
export class AddonCalendarCalendarComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
|
@Input() initialYear: number | string; // Initial year to load.
|
||||||
|
@Input() initialMonth: number | string; // Initial month to load.
|
||||||
|
@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.
|
||||||
|
@Input() displayNavButtons?: string | boolean; // Whether to display nav buttons created by this component. Defaults to true.
|
||||||
|
@Output() onEventClicked = new EventEmitter<number>();
|
||||||
|
@Output() onDayClicked = new EventEmitter<{day: number, month: number, year: number}>();
|
||||||
|
|
||||||
|
periodName: string;
|
||||||
|
weekDays: any[];
|
||||||
|
weeks: any[];
|
||||||
|
loaded = false;
|
||||||
|
timeFormat: string;
|
||||||
|
isCurrentMonth: boolean;
|
||||||
|
|
||||||
|
protected year: number;
|
||||||
|
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;
|
||||||
|
protected obsDefaultTimeChange: any;
|
||||||
|
|
||||||
|
constructor(eventsProvider: CoreEventsProvider,
|
||||||
|
sitesProvider: CoreSitesProvider,
|
||||||
|
localNotificationsProvider: CoreLocalNotificationsProvider,
|
||||||
|
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();
|
||||||
|
|
||||||
|
if (localNotificationsProvider.isAvailable()) {
|
||||||
|
// Re-schedule events if default time changes.
|
||||||
|
this.obsDefaultTimeChange = eventsProvider.on(AddonCalendarProvider.DEFAULT_NOTIFICATION_TIME_CHANGED, () => {
|
||||||
|
this.weeks.forEach((week) => {
|
||||||
|
week.days.forEach((day) => {
|
||||||
|
calendarProvider.scheduleEventsNotifications(day.events);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}, this.currentSiteId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component loaded.
|
||||||
|
*/
|
||||||
|
ngOnInit(): void {
|
||||||
|
const now = new Date();
|
||||||
|
|
||||||
|
this.year = this.initialYear ? Number(this.initialYear) : now.getFullYear();
|
||||||
|
this.month = this.initialMonth ? Number(this.initialMonth) : now.getMonth() + 1;
|
||||||
|
|
||||||
|
this.calculateIsCurrentMonth();
|
||||||
|
|
||||||
|
this.fetchData();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detect changes on input properties.
|
||||||
|
*/
|
||||||
|
ngOnChanges(changes: {[name: string]: SimpleChange}): void {
|
||||||
|
this.canNavigate = typeof this.canNavigate == 'undefined' ? true : this.utils.isTrueOrOne(this.canNavigate);
|
||||||
|
this.displayNavButtons = typeof this.displayNavButtons == 'undefined' ? true :
|
||||||
|
this.utils.isTrueOrOne(this.displayNavButtons);
|
||||||
|
|
||||||
|
if ((changes.courseId || changes.categoryId) && this.weeks) {
|
||||||
|
this.filterEvents();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch contacts.
|
||||||
|
*
|
||||||
|
* @param {boolean} [refresh=false] True if we are refreshing events.
|
||||||
|
* @return {Promise<any>} Promise resolved when done.
|
||||||
|
*/
|
||||||
|
fetchData(refresh: boolean = false): Promise<any> {
|
||||||
|
const promises = [];
|
||||||
|
|
||||||
|
promises.push(this.loadCategories());
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Get time format to use.
|
||||||
|
promises.push(this.calendarProvider.getCalendarTimeFormat().then((value) => {
|
||||||
|
this.timeFormat = value;
|
||||||
|
}));
|
||||||
|
|
||||||
|
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');
|
||||||
|
|
||||||
|
this.weekDays = this.calendarProvider.getWeekDays(result.daynames[0].dayno);
|
||||||
|
this.weeks = result.weeks;
|
||||||
|
|
||||||
|
// Merge the online events with offline data.
|
||||||
|
this.mergeEvents();
|
||||||
|
|
||||||
|
// Filter events by course.
|
||||||
|
this.filterEvents();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load categories to be able to filter events.
|
||||||
|
*
|
||||||
|
* @return {Promise<any>} Promise resolved when done.
|
||||||
|
*/
|
||||||
|
protected loadCategories(): Promise<any> {
|
||||||
|
if (this.categoriesRetrieved) {
|
||||||
|
// Already retrieved, stop.
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.coursesProvider.getCategories(0, true).then((cats) => {
|
||||||
|
this.categoriesRetrieved = true;
|
||||||
|
this.categories = {};
|
||||||
|
|
||||||
|
// Index categories by ID.
|
||||||
|
cats.forEach((category) => {
|
||||||
|
this.categories[category.id] = category;
|
||||||
|
});
|
||||||
|
}).catch(() => {
|
||||||
|
// Ignore errors.
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter events to only display events belonging to a certain course.
|
||||||
|
*/
|
||||||
|
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) => {
|
||||||
|
if (!courseId || courseId < 0) {
|
||||||
|
day.filteredEvents = day.events;
|
||||||
|
} else {
|
||||||
|
day.filteredEvents = day.events.filter((event) => {
|
||||||
|
return this.calendarHelper.shouldDisplayEvent(event, courseId, categoryId, this.categories);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-calculate some properties.
|
||||||
|
this.calendarHelper.calculateDayData(day, day.filteredEvents);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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(sync?: boolean, showErrors?: boolean): Promise<any> {
|
||||||
|
const promises = [];
|
||||||
|
|
||||||
|
promises.push(this.calendarProvider.invalidateMonthlyEvents(this.year, this.month));
|
||||||
|
promises.push(this.coursesProvider.invalidateCategories(0, true));
|
||||||
|
promises.push(this.calendarProvider.invalidateTimeFormat());
|
||||||
|
|
||||||
|
this.categoriesRetrieved = false; // Get categories again.
|
||||||
|
|
||||||
|
return Promise.all(promises).then(() => {
|
||||||
|
return this.fetchData(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load next month.
|
||||||
|
*/
|
||||||
|
loadNext(): void {
|
||||||
|
this.increaseMonth();
|
||||||
|
|
||||||
|
this.loaded = false;
|
||||||
|
|
||||||
|
this.fetchEvents().catch((error) => {
|
||||||
|
this.domUtils.showErrorModalDefault(error, 'addon.calendar.errorloadevents', true);
|
||||||
|
this.decreaseMonth();
|
||||||
|
}).finally(() => {
|
||||||
|
this.calculateIsCurrentMonth();
|
||||||
|
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.calculateIsCurrentMonth();
|
||||||
|
this.loaded = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An event was clicked.
|
||||||
|
*
|
||||||
|
* @param {any} event Event.
|
||||||
|
*/
|
||||||
|
eventClicked(event: any): void {
|
||||||
|
this.onEventClicked.emit(event.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A day was clicked.
|
||||||
|
*
|
||||||
|
* @param {number} day Day.
|
||||||
|
*/
|
||||||
|
dayClicked(day: number): void {
|
||||||
|
this.onDayClicked.emit({day: day, month: this.month, year: this.year});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if user is viewing the current month.
|
||||||
|
*/
|
||||||
|
calculateIsCurrentMonth(): void {
|
||||||
|
const now = new Date();
|
||||||
|
|
||||||
|
this.isCurrentMonth = this.year == now.getFullYear() && this.month == now.getMonth() + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Go to current month.
|
||||||
|
*/
|
||||||
|
goToCurrentMonth(): void {
|
||||||
|
const now = new Date(),
|
||||||
|
initialMonth = this.month,
|
||||||
|
initialYear = this.year;
|
||||||
|
|
||||||
|
this.month = now.getMonth() + 1;
|
||||||
|
this.year = now.getFullYear();
|
||||||
|
|
||||||
|
this.loaded = false;
|
||||||
|
|
||||||
|
this.fetchEvents().then(() => {
|
||||||
|
this.isCurrentMonth = true;
|
||||||
|
}).catch((error) => {
|
||||||
|
this.domUtils.showErrorModalDefault(error, 'addon.calendar.errorloadevents', true);
|
||||||
|
this.year = initialYear;
|
||||||
|
this.month = initialMonth;
|
||||||
|
}).finally(() => {
|
||||||
|
this.loaded = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrease the current month.
|
||||||
|
*/
|
||||||
|
protected decreaseMonth(): void {
|
||||||
|
if (this.month === 1) {
|
||||||
|
this.month = 12;
|
||||||
|
this.year--;
|
||||||
|
} else {
|
||||||
|
this.month--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Increase the current month.
|
||||||
|
*/
|
||||||
|
protected increaseMonth(): void {
|
||||||
|
if (this.month === 12) {
|
||||||
|
this.month = 1;
|
||||||
|
this.year++;
|
||||||
|
} else {
|
||||||
|
this.month++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merge online events with the offline events of that period.
|
||||||
|
*/
|
||||||
|
protected mergeEvents(): void {
|
||||||
|
const monthOfflineEvents = this.offlineEvents[this.calendarHelper.getMonthId(this.year, this.month)];
|
||||||
|
|
||||||
|
this.weeks.forEach((week) => {
|
||||||
|
week.days.forEach((day) => {
|
||||||
|
|
||||||
|
// Format online events.
|
||||||
|
day.events.forEach(this.calendarHelper.formatEventData.bind(this.calendarHelper));
|
||||||
|
|
||||||
|
// Schedule notifications for the events retrieved (only future events will be scheduled).
|
||||||
|
this.calendarProvider.scheduleEventsNotifications(day.events);
|
||||||
|
|
||||||
|
if (monthOfflineEvents || this.deletedEvents.length) {
|
||||||
|
// There is offline data, merge it.
|
||||||
|
|
||||||
|
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 {
|
||||||
|
this.undeleteEventObserver && this.undeleteEventObserver.off();
|
||||||
|
this.obsDefaultTimeChange && this.obsDefaultTimeChange.off();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
// (C) Copyright 2015 Martin Dougiamas
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { IonicModule } from 'ionic-angular';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { CoreComponentsModule } from '@components/components.module';
|
||||||
|
import { CoreDirectivesModule } from '@directives/directives.module';
|
||||||
|
import { CorePipesModule } from '@pipes/pipes.module';
|
||||||
|
import { AddonCalendarCalendarComponent } from '../components/calendar/calendar';
|
||||||
|
import { AddonCalendarUpcomingEventsComponent } from '../components/upcoming-events/upcoming-events';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
AddonCalendarCalendarComponent,
|
||||||
|
AddonCalendarUpcomingEventsComponent
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
IonicModule,
|
||||||
|
TranslateModule.forChild(),
|
||||||
|
CoreComponentsModule,
|
||||||
|
CoreDirectivesModule,
|
||||||
|
CorePipesModule
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
],
|
||||||
|
exports: [
|
||||||
|
AddonCalendarCalendarComponent,
|
||||||
|
AddonCalendarUpcomingEventsComponent
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class AddonCalendarComponentsModule {}
|
|
@ -0,0 +1,24 @@
|
||||||
|
<core-loading [hideUntil]="loaded" class="core-loading-center">
|
||||||
|
<core-empty-box *ngIf="!filteredEvents || !filteredEvents.length" icon="calendar" [message]="'addon.calendar.noevents' | translate">
|
||||||
|
</core-empty-box>
|
||||||
|
|
||||||
|
<ion-list *ngIf="filteredEvents && filteredEvents.length" no-margin>
|
||||||
|
<ng-container *ngFor="let event of filteredEvents">
|
||||||
|
<a ion-item text-wrap [title]="event.name" (click)="eventClicked(event)" [class.core-split-item-selected]="event.id == eventId">
|
||||||
|
<img *ngIf="event.moduleIcon" src="{{event.moduleIcon}}" item-start class="core-module-icon">
|
||||||
|
<core-icon *ngIf="event.icon && !event.moduleIcon" [name]="event.icon" item-start></core-icon>
|
||||||
|
<h2><core-format-text [text]="event.name"></core-format-text></h2>
|
||||||
|
<p><core-format-text [text]="event.formattedtime"></core-format-text></p>
|
||||||
|
<ion-note *ngIf="event.offline && !event.deleted" item-end>
|
||||||
|
<ion-icon name="time"></ion-icon>
|
||||||
|
<span text-wrap>{{ 'core.notsent' | translate }}</span>
|
||||||
|
</ion-note>
|
||||||
|
<ion-note *ngIf="event.deleted" item-end>
|
||||||
|
<ion-icon name="trash"></ion-icon>
|
||||||
|
<span text-wrap>{{ 'core.deletedoffline' | translate }}</span>
|
||||||
|
</ion-note>
|
||||||
|
</a>
|
||||||
|
</ng-container>
|
||||||
|
</ion-list>
|
||||||
|
|
||||||
|
</core-loading>
|
|
@ -0,0 +1,337 @@
|
||||||
|
// (C) Copyright 2015 Martin Dougiamas
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { Component, OnDestroy, OnInit, Input, OnChanges, SimpleChange, Output, EventEmitter } from '@angular/core';
|
||||||
|
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 { AddonCalendarHelperProvider } from '../../providers/helper';
|
||||||
|
import { AddonCalendarOfflineProvider } from '../../providers/calendar-offline';
|
||||||
|
import { CoreCoursesProvider } from '@core/courses/providers/courses';
|
||||||
|
import { CoreConstants } from '@core/constants';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component that displays upcoming events.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'addon-calendar-upcoming-events',
|
||||||
|
templateUrl: 'addon-calendar-upcoming-events.html',
|
||||||
|
})
|
||||||
|
export class AddonCalendarUpcomingEventsComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
|
@Input() courseId: number | string;
|
||||||
|
@Input() categoryId: number | string; // Category ID the course belongs to.
|
||||||
|
@Output() onEventClicked = new EventEmitter<number>();
|
||||||
|
|
||||||
|
filteredEvents = [];
|
||||||
|
loaded = false;
|
||||||
|
|
||||||
|
protected year: number;
|
||||||
|
protected month: number;
|
||||||
|
protected categoriesRetrieved = false;
|
||||||
|
protected categories = {};
|
||||||
|
protected currentSiteId: string;
|
||||||
|
protected events = []; // Events (both online and offline).
|
||||||
|
protected onlineEvents = [];
|
||||||
|
protected offlineEvents = []; // Offline events.
|
||||||
|
protected deletedEvents = []; // Events deleted in offline.
|
||||||
|
protected lookAhead: number;
|
||||||
|
protected timeFormat: string;
|
||||||
|
|
||||||
|
// Observers.
|
||||||
|
protected undeleteEventObserver: any;
|
||||||
|
protected obsDefaultTimeChange: any;
|
||||||
|
|
||||||
|
constructor(eventsProvider: CoreEventsProvider,
|
||||||
|
sitesProvider: CoreSitesProvider,
|
||||||
|
localNotificationsProvider: CoreLocalNotificationsProvider,
|
||||||
|
private calendarProvider: AddonCalendarProvider,
|
||||||
|
private calendarHelper: AddonCalendarHelperProvider,
|
||||||
|
private calendarOffline: AddonCalendarOfflineProvider,
|
||||||
|
private domUtils: CoreDomUtilsProvider,
|
||||||
|
private coursesProvider: CoreCoursesProvider) {
|
||||||
|
|
||||||
|
this.currentSiteId = sitesProvider.getCurrentSiteId();
|
||||||
|
|
||||||
|
if (localNotificationsProvider.isAvailable()) {
|
||||||
|
// Re-schedule events if default time changes.
|
||||||
|
this.obsDefaultTimeChange = eventsProvider.on(AddonCalendarProvider.DEFAULT_NOTIFICATION_TIME_CHANGED, () => {
|
||||||
|
calendarProvider.scheduleEventsNotifications(this.onlineEvents);
|
||||||
|
}, this.currentSiteId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component loaded.
|
||||||
|
*/
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.fetchData();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detect changes on input properties.
|
||||||
|
*/
|
||||||
|
ngOnChanges(changes: {[name: string]: SimpleChange}): void {
|
||||||
|
if (changes.courseId || changes.categoryId) {
|
||||||
|
this.filterEvents();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch data.
|
||||||
|
*
|
||||||
|
* @param {boolean} [refresh=false] True if we are refreshing events.
|
||||||
|
* @return {Promise<any>} Promise resolved when done.
|
||||||
|
*/
|
||||||
|
fetchData(refresh: boolean = false): Promise<any> {
|
||||||
|
const promises = [];
|
||||||
|
|
||||||
|
promises.push(this.loadCategories());
|
||||||
|
|
||||||
|
// Get offline events.
|
||||||
|
promises.push(this.calendarOffline.getAllEditedEvents().then((events) => {
|
||||||
|
// Format data.
|
||||||
|
events.forEach((event) => {
|
||||||
|
event.offline = true;
|
||||||
|
this.calendarHelper.formatEventData(event);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.offlineEvents = this.sortEvents(events);
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Get events deleted in offline.
|
||||||
|
promises.push(this.calendarOffline.getAllDeletedEventsIds().then((ids) => {
|
||||||
|
this.deletedEvents = ids;
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Get user preferences.
|
||||||
|
promises.push(this.calendarProvider.getCalendarLookAhead().then((value) => {
|
||||||
|
this.lookAhead = value;
|
||||||
|
}));
|
||||||
|
|
||||||
|
promises.push(this.calendarProvider.getCalendarTimeFormat().then((value) => {
|
||||||
|
this.timeFormat = value;
|
||||||
|
}));
|
||||||
|
|
||||||
|
return Promise.all(promises).then(() => {
|
||||||
|
return this.fetchEvents();
|
||||||
|
}).catch((error) => {
|
||||||
|
this.domUtils.showErrorModalDefault(error, 'addon.calendar.errorloadevents', true);
|
||||||
|
}).finally(() => {
|
||||||
|
this.loaded = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch upcoming events.
|
||||||
|
*
|
||||||
|
* @return {Promise<any>} Promise resolved when done.
|
||||||
|
*/
|
||||||
|
fetchEvents(): Promise<any> {
|
||||||
|
// Don't pass courseId and categoryId, we'll filter them locally.
|
||||||
|
return this.calendarProvider.getUpcomingEvents().then((result) => {
|
||||||
|
const promises = [];
|
||||||
|
|
||||||
|
this.onlineEvents = result.events;
|
||||||
|
|
||||||
|
this.onlineEvents.forEach(this.calendarHelper.formatEventData.bind(this.calendarHelper));
|
||||||
|
|
||||||
|
// Schedule notifications for the events retrieved.
|
||||||
|
this.calendarProvider.scheduleEventsNotifications(this.onlineEvents);
|
||||||
|
|
||||||
|
// Merge the online events with offline data.
|
||||||
|
this.events = this.mergeEvents();
|
||||||
|
|
||||||
|
// Filter events by course.
|
||||||
|
this.filterEvents();
|
||||||
|
|
||||||
|
// Re-calculate the formatted time so it uses the device date.
|
||||||
|
this.events.forEach((event) => {
|
||||||
|
promises.push(this.calendarProvider.formatEventTime(event, this.timeFormat).then((time) => {
|
||||||
|
event.formattedtime = time;
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
return Promise.all(promises);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load categories to be able to filter events.
|
||||||
|
*
|
||||||
|
* @return {Promise<any>} Promise resolved when done.
|
||||||
|
*/
|
||||||
|
protected loadCategories(): Promise<any> {
|
||||||
|
if (this.categoriesRetrieved) {
|
||||||
|
// Already retrieved, stop.
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.coursesProvider.getCategories(0, true).then((cats) => {
|
||||||
|
this.categoriesRetrieved = true;
|
||||||
|
this.categories = {};
|
||||||
|
|
||||||
|
// Index categories by ID.
|
||||||
|
cats.forEach((category) => {
|
||||||
|
this.categories[category.id] = category;
|
||||||
|
});
|
||||||
|
}).catch(() => {
|
||||||
|
// Ignore errors.
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter events to only display events belonging to a certain course.
|
||||||
|
*/
|
||||||
|
filterEvents(): void {
|
||||||
|
const courseId = this.courseId ? Number(this.courseId) : undefined,
|
||||||
|
categoryId = this.categoryId ? Number(this.categoryId) : undefined;
|
||||||
|
|
||||||
|
if (!courseId || courseId < 0) {
|
||||||
|
this.filteredEvents = this.events;
|
||||||
|
} else {
|
||||||
|
this.filteredEvents = this.events.filter((event) => {
|
||||||
|
return this.calendarHelper.shouldDisplayEvent(event, courseId, categoryId, this.categories);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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(sync?: boolean, showErrors?: boolean): Promise<any> {
|
||||||
|
const promises = [];
|
||||||
|
|
||||||
|
promises.push(this.calendarProvider.invalidateAllUpcomingEvents());
|
||||||
|
promises.push(this.coursesProvider.invalidateCategories(0, true));
|
||||||
|
promises.push(this.calendarProvider.invalidateLookAhead());
|
||||||
|
promises.push(this.calendarProvider.invalidateTimeFormat());
|
||||||
|
|
||||||
|
this.categoriesRetrieved = false; // Get categories again.
|
||||||
|
|
||||||
|
return Promise.all(promises).then(() => {
|
||||||
|
return this.fetchData(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An event was clicked.
|
||||||
|
*
|
||||||
|
* @param {any} event Event.
|
||||||
|
*/
|
||||||
|
eventClicked(event: any): void {
|
||||||
|
this.onEventClicked.emit(event.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merge online events with the offline events of that period.
|
||||||
|
*
|
||||||
|
* @return {any[]} Merged events.
|
||||||
|
*/
|
||||||
|
protected mergeEvents(): any[] {
|
||||||
|
if (!this.offlineEvents.length && !this.deletedEvents.length) {
|
||||||
|
// No offline events, nothing to merge.
|
||||||
|
return this.onlineEvents;
|
||||||
|
}
|
||||||
|
|
||||||
|
const start = Date.now(),
|
||||||
|
end = start + (CoreConstants.SECONDS_DAY * this.lookAhead);
|
||||||
|
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 (this.offlineEvents.length) {
|
||||||
|
// Remove the online events that were modified in offline.
|
||||||
|
result = result.filter((event) => {
|
||||||
|
const offlineEvent = this.offlineEvents.find((ev) => {
|
||||||
|
return ev.id == event.id;
|
||||||
|
});
|
||||||
|
|
||||||
|
return !offlineEvent;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now get the offline events that belong to this period.
|
||||||
|
const periodOfflineEvents = this.offlineEvents.filter((event) => {
|
||||||
|
return (event.timestart >= start || event.timestart + event.timeduration >= start) && event.timestart <= end;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Merge both arrays and sort them.
|
||||||
|
result = result.concat(periodOfflineEvents);
|
||||||
|
|
||||||
|
return this.sortEvents(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 {
|
||||||
|
const event = this.onlineEvents.find((event) => {
|
||||||
|
return event.id == eventId;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (event) {
|
||||||
|
event.deleted = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component destroyed.
|
||||||
|
*/
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.undeleteEventObserver && this.undeleteEventObserver.off();
|
||||||
|
this.obsDefaultTimeChange && this.obsDefaultTimeChange.off();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +1,13 @@
|
||||||
{
|
{
|
||||||
|
"allday": "All day",
|
||||||
"calendar": "Calendar",
|
"calendar": "Calendar",
|
||||||
"calendarevent": "Calendar event",
|
"calendarevent": "Calendar event",
|
||||||
"calendarevents": "Calendar events",
|
"calendarevents": "Calendar events",
|
||||||
"calendarreminders": "Calendar reminders",
|
"calendarreminders": "Calendar reminders",
|
||||||
"confirmeventdelete": "Are you sure you want to delete the \"{{$a}}\" event?",
|
"confirmeventdelete": "Are you sure you want to delete the \"{{$a}}\" event?",
|
||||||
"confirmeventseriesdelete": "The \"{{$a.name}}\" event is part of a series. Do you want to delete just this event, or all {{$a.count}} events in the series?",
|
"confirmeventseriesdelete": "The \"{{$a.name}}\" event is part of a series. Do you want to delete just this event, or all {{$a.count}} events in the series?",
|
||||||
|
"daynext": "Next day",
|
||||||
|
"dayprev": "Previous day",
|
||||||
"defaultnotificationtime": "Default notification time",
|
"defaultnotificationtime": "Default notification time",
|
||||||
"deleteallevents": "Delete all events",
|
"deleteallevents": "Delete all events",
|
||||||
"deleteevent": "Delete event",
|
"deleteevent": "Delete event",
|
||||||
|
@ -22,9 +25,14 @@
|
||||||
"eventname": "Event title",
|
"eventname": "Event title",
|
||||||
"eventstarttime": "Start time",
|
"eventstarttime": "Start time",
|
||||||
"eventtype": "Event type",
|
"eventtype": "Event type",
|
||||||
|
"fri": "Fri",
|
||||||
|
"friday": "Friday",
|
||||||
"gotoactivity": "Go to activity",
|
"gotoactivity": "Go to activity",
|
||||||
"invalidtimedurationminutes": "The duration in minutes you have entered is invalid. Please enter the duration in minutes greater than 0 or select no duration.",
|
"invalidtimedurationminutes": "The duration in minutes you have entered is invalid. Please enter the duration in minutes greater than 0 or select no duration.",
|
||||||
"invalidtimedurationuntil": "The date and time you selected for duration until is before the start time of the event. Please correct this before proceeding.",
|
"invalidtimedurationuntil": "The date and time you selected for duration until is before the start time of the event. Please correct this before proceeding.",
|
||||||
|
"mon": "Mon",
|
||||||
|
"monday": "Monday",
|
||||||
|
"monthlyview": "Monthly view",
|
||||||
"newevent": "New event",
|
"newevent": "New event",
|
||||||
"noevents": "There are no events",
|
"noevents": "There are no events",
|
||||||
"nopermissiontoupdatecalendar": "Sorry, but you do not have permission to update the calendar event",
|
"nopermissiontoupdatecalendar": "Sorry, but you do not have permission to update the calendar event",
|
||||||
|
@ -34,7 +42,17 @@
|
||||||
"repeateditthis": "Apply changes to this event only",
|
"repeateditthis": "Apply changes to this event only",
|
||||||
"repeatevent": "Repeat this event",
|
"repeatevent": "Repeat this event",
|
||||||
"repeatweeksl": "Repeat weekly, creating altogether",
|
"repeatweeksl": "Repeat weekly, creating altogether",
|
||||||
|
"sat": "Sat",
|
||||||
|
"saturday": "Saturday",
|
||||||
"setnewreminder": "Set a new reminder",
|
"setnewreminder": "Set a new reminder",
|
||||||
|
"sun": "Sun",
|
||||||
|
"sunday": "Sunday",
|
||||||
|
"thu": "Thu",
|
||||||
|
"thursday": "Thursday",
|
||||||
|
"today": "Today",
|
||||||
|
"tomorrow": "Tomorrow",
|
||||||
|
"tue": "Tue",
|
||||||
|
"tuesday": "Tuesday",
|
||||||
"typeclose": "Close event",
|
"typeclose": "Close event",
|
||||||
"typecourse": "Course event",
|
"typecourse": "Course event",
|
||||||
"typecategory": "Category event",
|
"typecategory": "Category event",
|
||||||
|
@ -43,5 +61,10 @@
|
||||||
"typegroup": "Group event",
|
"typegroup": "Group event",
|
||||||
"typeopen": "Open event",
|
"typeopen": "Open event",
|
||||||
"typesite": "Site event",
|
"typesite": "Site event",
|
||||||
"typeuser": "User event"
|
"typeuser": "User event",
|
||||||
|
"upcomingevents": "Upcoming events",
|
||||||
|
"wed": "Wed",
|
||||||
|
"wednesday": "Wednesday",
|
||||||
|
"when": "When",
|
||||||
|
"yesterday": "Yesterday"
|
||||||
}
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
<ion-header>
|
||||||
|
<ion-navbar core-back-button>
|
||||||
|
<ion-title>{{ 'addon.calendar.calendarevents' | translate }}</ion-title>
|
||||||
|
<ion-buttons end>
|
||||||
|
<button *ngIf="courses && courses.length" ion-button icon-only (click)="openCourseFilter($event)" [attr.aria-label]="'core.courses.filter' | translate">
|
||||||
|
<ion-icon name="funnel"></ion-icon>
|
||||||
|
</button>
|
||||||
|
<button *ngIf="!isCurrentDay" ion-button icon-only clear (click)="goToCurrentDay()">
|
||||||
|
<core-icon name="fa-calendar-times-o"></core-icon>
|
||||||
|
</button>
|
||||||
|
<core-context-menu>
|
||||||
|
<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>
|
||||||
|
</ion-header>
|
||||||
|
<ion-content>
|
||||||
|
<ion-refresher [enabled]="loaded" (ionRefresh)="doRefresh($event)">
|
||||||
|
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||||
|
</ion-refresher>
|
||||||
|
|
||||||
|
<!-- Period name and arrows to navigate. -->
|
||||||
|
<ion-grid padding-top>
|
||||||
|
<ion-row>
|
||||||
|
<ion-col text-start *ngIf="currentMoment">
|
||||||
|
<a ion-button icon-only clear (click)="loadPrevious()" [title]="'addon.calendar.dayprev' | translate">
|
||||||
|
<ion-icon name="arrow-back" md="ios-arrow-back"></ion-icon>
|
||||||
|
</a>
|
||||||
|
</ion-col>
|
||||||
|
<ion-col text-center class="addon-calendar-period">
|
||||||
|
<h3>{{ periodName }}</h3>
|
||||||
|
</ion-col>
|
||||||
|
<ion-col text-end *ngIf="currentMoment">
|
||||||
|
<a ion-button icon-only clear (click)="loadNext()" [title]="'addon.calendar.daynext' | translate">
|
||||||
|
<ion-icon name="arrow-forward" md="ios-arrow-forward"></ion-icon>
|
||||||
|
</a>
|
||||||
|
</ion-col>
|
||||||
|
</ion-row>
|
||||||
|
</ion-grid>
|
||||||
|
|
||||||
|
<!-- 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: 'core.day' | translate} }}
|
||||||
|
</ion-card>
|
||||||
|
|
||||||
|
<core-empty-box *ngIf="!filteredEvents || !filteredEvents.length" icon="calendar" [message]="'addon.calendar.noevents' | translate">
|
||||||
|
</core-empty-box>
|
||||||
|
|
||||||
|
<ion-list *ngIf="filteredEvents && filteredEvents.length" no-margin>
|
||||||
|
<ng-container *ngFor="let event of filteredEvents">
|
||||||
|
<a ion-item text-wrap [title]="event.name" (click)="gotoEvent(event.id)" [class.core-split-item-selected]="event.id == eventId">
|
||||||
|
<img *ngIf="event.moduleIcon" src="{{event.moduleIcon}}" item-start class="core-module-icon">
|
||||||
|
<core-icon *ngIf="event.icon && !event.moduleIcon" [name]="event.icon" item-start></core-icon>
|
||||||
|
<h2><core-format-text [text]="event.name"></core-format-text></h2>
|
||||||
|
<p><core-format-text [text]="event.formattedtime"></core-format-text></p>
|
||||||
|
<ion-note *ngIf="event.offline && !event.deleted" item-end>
|
||||||
|
<ion-icon name="time"></ion-icon>
|
||||||
|
<span text-wrap>{{ 'core.notsent' | translate }}</span>
|
||||||
|
</ion-note>
|
||||||
|
<ion-note *ngIf="event.deleted" item-end>
|
||||||
|
<ion-icon name="trash"></ion-icon>
|
||||||
|
<span text-wrap>{{ 'core.deletedoffline' | translate }}</span>
|
||||||
|
</ion-note>
|
||||||
|
</a>
|
||||||
|
</ng-container>
|
||||||
|
</ion-list>
|
||||||
|
|
||||||
|
<!-- Create a calendar event. -->
|
||||||
|
<ion-fab core-fab bottom end *ngIf="canCreate">
|
||||||
|
<button ion-fab (click)="openEdit()" [attr.aria-label]="'addon.calendar.newevent' | translate">
|
||||||
|
<ion-icon name="add"></ion-icon>
|
||||||
|
</button>
|
||||||
|
</ion-fab>
|
||||||
|
</ion-content>
|
|
@ -0,0 +1,35 @@
|
||||||
|
// (C) Copyright 2015 Martin Dougiamas
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { IonicPageModule } from 'ionic-angular';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { CoreComponentsModule } from '@components/components.module';
|
||||||
|
import { CoreDirectivesModule } from '@directives/directives.module';
|
||||||
|
import { CorePipesModule } from '@pipes/pipes.module';
|
||||||
|
import { AddonCalendarDayPage } from './day';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
AddonCalendarDayPage,
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
CoreComponentsModule,
|
||||||
|
CoreDirectivesModule,
|
||||||
|
CorePipesModule,
|
||||||
|
IonicPageModule.forChild(AddonCalendarDayPage),
|
||||||
|
TranslateModule.forChild()
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class AddonCalendarDayPageModule {}
|
|
@ -0,0 +1,9 @@
|
||||||
|
page-addon-calendar-day {
|
||||||
|
.addon-calendar-period {
|
||||||
|
flex-grow: 3;
|
||||||
|
h3 {
|
||||||
|
margin-top: 10px;
|
||||||
|
font-size: 1.6rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,675 @@
|
||||||
|
// (C) Copyright 2015 Martin Dougiamas
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OFx ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { Component, OnInit, OnDestroy, NgZone } from '@angular/core';
|
||||||
|
import { IonicPage, NavParams, NavController } 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 { CoreTimeUtilsProvider } from '@providers/utils/time';
|
||||||
|
import { AddonCalendarProvider } from '../../providers/calendar';
|
||||||
|
import { AddonCalendarOfflineProvider } from '../../providers/calendar-offline';
|
||||||
|
import { AddonCalendarHelperProvider } from '../../providers/helper';
|
||||||
|
import { AddonCalendarSyncProvider } from '../../providers/calendar-sync';
|
||||||
|
import { CoreCoursesProvider } from '@core/courses/providers/courses';
|
||||||
|
import { CoreCoursesHelperProvider } from '@core/courses/providers/helper';
|
||||||
|
import { Network } from '@ionic-native/network';
|
||||||
|
import * as moment from 'moment';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Page that displays the calendar events for a certain day.
|
||||||
|
*/
|
||||||
|
@IonicPage({ segment: 'addon-calendar-day' })
|
||||||
|
@Component({
|
||||||
|
selector: 'page-addon-calendar-day',
|
||||||
|
templateUrl: 'day.html',
|
||||||
|
})
|
||||||
|
export class AddonCalendarDayPage implements OnInit, OnDestroy {
|
||||||
|
|
||||||
|
protected currentSiteId: string;
|
||||||
|
protected year: number;
|
||||||
|
protected month: number;
|
||||||
|
protected day: number;
|
||||||
|
protected categories = {};
|
||||||
|
protected events = []; // Events (both online and offline).
|
||||||
|
protected onlineEvents = [];
|
||||||
|
protected offlineEvents = {}; // Offline events.
|
||||||
|
protected offlineEditedEventsIds = []; // IDs of events edited in offline.
|
||||||
|
protected deletedEvents = []; // Events deleted in offline.
|
||||||
|
protected timeFormat: string;
|
||||||
|
protected currentMoment: moment.Moment;
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
protected obsDefaultTimeChange: any;
|
||||||
|
|
||||||
|
periodName: string;
|
||||||
|
filteredEvents = [];
|
||||||
|
courseId: number;
|
||||||
|
categoryId: number;
|
||||||
|
canCreate = false;
|
||||||
|
courses: any[];
|
||||||
|
loaded = false;
|
||||||
|
hasOffline = false;
|
||||||
|
isOnline = false;
|
||||||
|
syncIcon: string;
|
||||||
|
isCurrentDay: boolean;
|
||||||
|
|
||||||
|
constructor(localNotificationsProvider: CoreLocalNotificationsProvider,
|
||||||
|
navParams: NavParams,
|
||||||
|
network: Network,
|
||||||
|
zone: NgZone,
|
||||||
|
sitesProvider: CoreSitesProvider,
|
||||||
|
private navCtrl: NavController,
|
||||||
|
private domUtils: CoreDomUtilsProvider,
|
||||||
|
private timeUtils: CoreTimeUtilsProvider,
|
||||||
|
private calendarProvider: AddonCalendarProvider,
|
||||||
|
private calendarOffline: AddonCalendarOfflineProvider,
|
||||||
|
private calendarHelper: AddonCalendarHelperProvider,
|
||||||
|
private calendarSync: AddonCalendarSyncProvider,
|
||||||
|
private eventsProvider: CoreEventsProvider,
|
||||||
|
private coursesProvider: CoreCoursesProvider,
|
||||||
|
private coursesHelper: CoreCoursesHelperProvider,
|
||||||
|
private appProvider: CoreAppProvider) {
|
||||||
|
|
||||||
|
const now = new Date();
|
||||||
|
|
||||||
|
this.year = navParams.get('year') || now.getFullYear();
|
||||||
|
this.month = navParams.get('month') || (now.getMonth() + 1);
|
||||||
|
this.day = navParams.get('day') || now.getDate();
|
||||||
|
this.courseId = navParams.get('courseId');
|
||||||
|
this.currentSiteId = sitesProvider.getCurrentSiteId();
|
||||||
|
|
||||||
|
if (localNotificationsProvider.isAvailable()) {
|
||||||
|
// Re-schedule events if default time changes.
|
||||||
|
this.obsDefaultTimeChange = eventsProvider.on(AddonCalendarProvider.DEFAULT_NOTIFICATION_TIME_CHANGED, () => {
|
||||||
|
calendarProvider.scheduleEventsNotifications(this.onlineEvents);
|
||||||
|
}, this.currentSiteId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 != 'day' || data.year != this.year || data.month != this.month || data.day != this.day)) {
|
||||||
|
this.loaded = false;
|
||||||
|
this.refreshData();
|
||||||
|
}
|
||||||
|
}, this.currentSiteId);
|
||||||
|
|
||||||
|
// Update the events when an event is deleted.
|
||||||
|
this.deleteEventObserver = eventsProvider.on(AddonCalendarProvider.DELETED_EVENT_EVENT, (data) => {
|
||||||
|
if (data && !data.sent) {
|
||||||
|
// Event was deleted in offline. Just mark it as deleted, no need to refresh.
|
||||||
|
this.hasOffline = this.markAsDeleted(data.eventId, true) || this.hasOffline;
|
||||||
|
this.deletedEvents.push(data.eventId);
|
||||||
|
} else {
|
||||||
|
this.loaded = false;
|
||||||
|
this.refreshData();
|
||||||
|
}
|
||||||
|
}, this.currentSiteId);
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
const found = this.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) => {
|
||||||
|
return event.deleted || event.offline;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.hasOffline = !!event;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 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.calculateCurrentMoment();
|
||||||
|
this.calculateIsCurrentDay();
|
||||||
|
|
||||||
|
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(sync?: boolean, showErrors?: boolean): Promise<any> {
|
||||||
|
|
||||||
|
this.syncIcon = 'spinner';
|
||||||
|
this.isOnline = this.appProvider.isOnline();
|
||||||
|
|
||||||
|
const promise = sync ? this.sync() : Promise.resolve();
|
||||||
|
|
||||||
|
return promise.then(() => {
|
||||||
|
const promises = [];
|
||||||
|
|
||||||
|
// Load courses for the popover.
|
||||||
|
promises.push(this.coursesHelper.getCoursesForPopover(this.courseId).then((data) => {
|
||||||
|
this.courses = data.courses;
|
||||||
|
this.categoryId = data.categoryId;
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Get categories.
|
||||||
|
promises.push(this.loadCategories());
|
||||||
|
|
||||||
|
// 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 & day.
|
||||||
|
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;
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Check if user can create events.
|
||||||
|
promises.push(this.calendarHelper.canEditEvents(this.courseId).then((canEdit) => {
|
||||||
|
this.canCreate = canEdit;
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Get user preferences.
|
||||||
|
promises.push(this.calendarProvider.getCalendarTimeFormat().then((value) => {
|
||||||
|
this.timeFormat = value;
|
||||||
|
}));
|
||||||
|
|
||||||
|
return Promise.all(promises);
|
||||||
|
}).then(() => {
|
||||||
|
return this.fetchEvents();
|
||||||
|
}).catch((error) => {
|
||||||
|
this.domUtils.showErrorModalDefault(error, 'addon.calendar.errorloadevents', true);
|
||||||
|
}).finally(() => {
|
||||||
|
this.loaded = true;
|
||||||
|
this.syncIcon = 'sync';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch the events for current day.
|
||||||
|
*
|
||||||
|
* @return {Promise<any>} Promise resolved when done.
|
||||||
|
*/
|
||||||
|
fetchEvents(): Promise<any> {
|
||||||
|
// Don't pass courseId and categoryId, we'll filter them locally.
|
||||||
|
return this.calendarProvider.getDayEvents(this.year, this.month, this.day).then((result) => {
|
||||||
|
const promises = [];
|
||||||
|
|
||||||
|
// 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, this.day).getTime(),
|
||||||
|
'core.strftimedaydate');
|
||||||
|
|
||||||
|
this.onlineEvents = result.events;
|
||||||
|
this.onlineEvents.forEach(this.calendarHelper.formatEventData.bind(this.calendarHelper));
|
||||||
|
|
||||||
|
// Schedule notifications for the events retrieved (only future events will be scheduled).
|
||||||
|
this.calendarProvider.scheduleEventsNotifications(this.onlineEvents);
|
||||||
|
|
||||||
|
// Merge the online events with offline data.
|
||||||
|
this.events = this.mergeEvents();
|
||||||
|
|
||||||
|
// Filter events by course.
|
||||||
|
this.filterEvents();
|
||||||
|
|
||||||
|
// Re-calculate the formatted time so it uses the device date.
|
||||||
|
const dayTime = this.currentMoment.unix() * 1000;
|
||||||
|
this.events.forEach((event) => {
|
||||||
|
promises.push(this.calendarProvider.formatEventTime(event, this.timeFormat, true, dayTime).then((time) => {
|
||||||
|
event.formattedtime = time;
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
return Promise.all(promises);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merge online events with the offline events of that period.
|
||||||
|
*
|
||||||
|
* @return {any[]} Merged events.
|
||||||
|
*/
|
||||||
|
protected mergeEvents(): any[] {
|
||||||
|
this.hasOffline = false;
|
||||||
|
|
||||||
|
if (!this.offlineEditedEventsIds.length && !this.deletedEvents.length) {
|
||||||
|
// No offline events, nothing to merge.
|
||||||
|
return this.onlineEvents;
|
||||||
|
}
|
||||||
|
|
||||||
|
const monthOfflineEvents = this.offlineEvents[this.calendarHelper.getMonthId(this.year, this.month)],
|
||||||
|
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) => {
|
||||||
|
return 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 = this.sortEvents(result.concat(dayOfflineEvents));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter events to only display events belonging to a certain course.
|
||||||
|
*/
|
||||||
|
protected filterEvents(): void {
|
||||||
|
if (!this.courseId || this.courseId < 0) {
|
||||||
|
this.filteredEvents = this.events;
|
||||||
|
} else {
|
||||||
|
this.filteredEvents = this.events.filter((event) => {
|
||||||
|
return this.calendarHelper.shouldDisplayEvent(event, this.courseId, this.categoryId, this.categories);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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, 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());
|
||||||
|
promises.push(this.calendarProvider.invalidateDayEvents(this.year, this.month, this.day));
|
||||||
|
promises.push(this.coursesProvider.invalidateCategories(0, true));
|
||||||
|
promises.push(this.calendarProvider.invalidateTimeFormat());
|
||||||
|
|
||||||
|
return Promise.all(promises).finally(() => {
|
||||||
|
return this.fetchData(sync, showErrors);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load categories to be able to filter events.
|
||||||
|
*
|
||||||
|
* @return {Promise<any>} Promise resolved when done.
|
||||||
|
*/
|
||||||
|
protected loadCategories(): Promise<any> {
|
||||||
|
return this.coursesProvider.getCategories(0, true).then((cats) => {
|
||||||
|
this.categories = {};
|
||||||
|
|
||||||
|
// Index categories by ID.
|
||||||
|
cats.forEach((category) => {
|
||||||
|
this.categories[category.id] = category;
|
||||||
|
});
|
||||||
|
}).catch(() => {
|
||||||
|
// Ignore errors.
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to synchronize offline events.
|
||||||
|
*
|
||||||
|
* @param {boolean} [showErrors] Whether to show sync errors to the user.
|
||||||
|
* @return {Promise<any>} Promise resolved when done.
|
||||||
|
*/
|
||||||
|
protected sync(showErrors?: boolean): Promise<any> {
|
||||||
|
return this.calendarSync.syncEvents().then((result) => {
|
||||||
|
if (result.warnings && result.warnings.length) {
|
||||||
|
this.domUtils.showErrorModal(result.warnings[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.updated) {
|
||||||
|
// Trigger a manual sync event.
|
||||||
|
result.source = 'day';
|
||||||
|
result.day = this.day;
|
||||||
|
result.month = this.month;
|
||||||
|
result.year = this.year;
|
||||||
|
|
||||||
|
this.eventsProvider.trigger(AddonCalendarSyncProvider.MANUAL_SYNCED, result, this.currentSiteId);
|
||||||
|
}
|
||||||
|
}).catch((error) => {
|
||||||
|
if (showErrors) {
|
||||||
|
this.domUtils.showErrorModalDefault(error, 'core.errorsync', true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* @param {MouseEvent} event Event.
|
||||||
|
*/
|
||||||
|
openCourseFilter(event: MouseEvent): void {
|
||||||
|
this.coursesHelper.selectCourse(event, this.courses, this.courseId).then((result) => {
|
||||||
|
if (typeof result.courseId != 'undefined') {
|
||||||
|
this.courseId = result.courseId > 0 ? result.courseId : undefined;
|
||||||
|
this.categoryId = result.courseId > 0 ? result.categoryId : undefined;
|
||||||
|
|
||||||
|
// Course viewed has changed, check if the user can create events for this course calendar.
|
||||||
|
this.calendarHelper.canEditEvents(this.courseId).then((canEdit) => {
|
||||||
|
this.canCreate = canEdit;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.filterEvents();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open page to create/edit an event.
|
||||||
|
*
|
||||||
|
* @param {number} [eventId] Event ID to edit.
|
||||||
|
*/
|
||||||
|
openEdit(eventId?: number): void {
|
||||||
|
const params: any = {};
|
||||||
|
|
||||||
|
if (eventId) {
|
||||||
|
params.eventId = eventId;
|
||||||
|
} else {
|
||||||
|
// It's a new event, set the time.
|
||||||
|
params.timestamp = moment().year(this.year).month(this.month - 1).date(this.day).unix() * 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.courseId) {
|
||||||
|
params.courseId = this.courseId;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.navCtrl.push('AddonCalendarEditEventPage', 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.
|
||||||
|
*/
|
||||||
|
calculateIsCurrentDay(): void {
|
||||||
|
const now = new Date();
|
||||||
|
|
||||||
|
this.isCurrentDay = this.year == now.getFullYear() && this.month == now.getMonth() + 1 && this.day == now.getDate();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Go to current day.
|
||||||
|
*/
|
||||||
|
goToCurrentDay(): void {
|
||||||
|
const now = new Date(),
|
||||||
|
initialDay = this.day,
|
||||||
|
initialMonth = this.month,
|
||||||
|
initialYear = this.year;
|
||||||
|
|
||||||
|
this.day = now.getDate();
|
||||||
|
this.month = now.getMonth() + 1;
|
||||||
|
this.year = now.getFullYear();
|
||||||
|
this.calculateCurrentMoment();
|
||||||
|
|
||||||
|
this.loaded = false;
|
||||||
|
|
||||||
|
this.fetchEvents().then(() => {
|
||||||
|
this.isCurrentDay = true;
|
||||||
|
}).catch((error) => {
|
||||||
|
this.domUtils.showErrorModalDefault(error, 'addon.calendar.errorloadevents', true);
|
||||||
|
|
||||||
|
this.year = initialYear;
|
||||||
|
this.month = initialMonth;
|
||||||
|
this.day = initialDay;
|
||||||
|
this.calculateCurrentMoment();
|
||||||
|
}).finally(() => {
|
||||||
|
this.loaded = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load next month.
|
||||||
|
*/
|
||||||
|
loadNext(): void {
|
||||||
|
this.increaseDay();
|
||||||
|
|
||||||
|
this.loaded = false;
|
||||||
|
|
||||||
|
this.fetchEvents().catch((error) => {
|
||||||
|
this.domUtils.showErrorModalDefault(error, 'addon.calendar.errorloadevents', true);
|
||||||
|
this.decreaseDay();
|
||||||
|
}).finally(() => {
|
||||||
|
this.calculateIsCurrentDay();
|
||||||
|
this.loaded = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load previous month.
|
||||||
|
*/
|
||||||
|
loadPrevious(): void {
|
||||||
|
this.decreaseDay();
|
||||||
|
|
||||||
|
this.loaded = false;
|
||||||
|
|
||||||
|
this.fetchEvents().catch((error) => {
|
||||||
|
this.domUtils.showErrorModalDefault(error, 'addon.calendar.errorloadevents', true);
|
||||||
|
this.increaseDay();
|
||||||
|
}).finally(() => {
|
||||||
|
this.calculateIsCurrentDay();
|
||||||
|
this.loaded = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrease the current day.
|
||||||
|
*/
|
||||||
|
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 {number} eventId Event ID.
|
||||||
|
* @param {boolean} deleted Whether to mark it as deleted or not.
|
||||||
|
* @return {boolean} Whether the event was found.
|
||||||
|
*/
|
||||||
|
protected markAsDeleted(eventId: number, deleted: boolean): boolean {
|
||||||
|
const event = this.onlineEvents.find((event) => {
|
||||||
|
return event.id == eventId;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (event) {
|
||||||
|
event.deleted = deleted;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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();
|
||||||
|
this.obsDefaultTimeChange && this.obsDefaultTimeChange.off();
|
||||||
|
}
|
||||||
|
}
|
|
@ -99,6 +99,8 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy {
|
||||||
this.courseId = navParams.get('courseId');
|
this.courseId = navParams.get('courseId');
|
||||||
this.title = this.eventId ? 'addon.calendar.editevent' : 'addon.calendar.newevent';
|
this.title = this.eventId ? 'addon.calendar.editevent' : 'addon.calendar.newevent';
|
||||||
|
|
||||||
|
const timestamp = navParams.get('timestamp');
|
||||||
|
|
||||||
this.currentSite = sitesProvider.getCurrentSite();
|
this.currentSite = sitesProvider.getCurrentSite();
|
||||||
this.errors = {
|
this.errors = {
|
||||||
required: this.translate.instant('core.required')
|
required: this.translate.instant('core.required')
|
||||||
|
@ -114,7 +116,7 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy {
|
||||||
this.groupControl = this.fb.control('');
|
this.groupControl = this.fb.control('');
|
||||||
this.descriptionControl = this.fb.control('');
|
this.descriptionControl = this.fb.control('');
|
||||||
|
|
||||||
const currentDate = this.timeUtils.toDatetimeFormat();
|
const currentDate = this.timeUtils.toDatetimeFormat(timestamp);
|
||||||
|
|
||||||
this.eventForm.addControl('name', this.fb.control('', Validators.required));
|
this.eventForm.addControl('name', this.fb.control('', Validators.required));
|
||||||
this.eventForm.addControl('timestart', this.fb.control(currentDate, Validators.required));
|
this.eventForm.addControl('timestart', this.fb.control(currentDate, Validators.required));
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
<ion-header>
|
<ion-header>
|
||||||
<ion-navbar core-back-button>
|
<ion-navbar core-back-button>
|
||||||
<ion-title><core-format-text [text]="title"></core-format-text></ion-title>
|
<ion-title>
|
||||||
|
<img *ngIf="event && event.moduleIcon" src="{{event.moduleIcon}}" alt="" role="presentation" class="core-module-icon">
|
||||||
|
<core-icon *ngIf="event && event.icon && !event.moduleIcon" [name]="event.icon" item-start></core-icon>
|
||||||
|
<core-format-text *ngIf="event" [text]="event.name"></core-format-text>
|
||||||
|
</ion-title>
|
||||||
<ion-buttons end>
|
<ion-buttons end>
|
||||||
<!-- The context menu will be added in here. -->
|
<!-- The context menu will be added in here. -->
|
||||||
</ion-buttons>
|
</ion-buttons>
|
||||||
|
@ -26,20 +30,25 @@
|
||||||
|
|
||||||
<ion-card>
|
<ion-card>
|
||||||
<ion-card-content *ngIf="event">
|
<ion-card-content *ngIf="event">
|
||||||
<ion-item text-wrap>
|
<ion-item text-wrap *ngIf="isSplitViewOn">
|
||||||
|
<img *ngIf="event.moduleIcon" src="{{event.moduleIcon}}" item-start alt="" role="presentation" class="core-module-icon">
|
||||||
<core-icon *ngIf="event.icon && !event.moduleIcon" [name]="event.icon" item-start></core-icon>
|
<core-icon *ngIf="event.icon && !event.moduleIcon" [name]="event.icon" item-start></core-icon>
|
||||||
<h2><core-format-text [text]="event.name"></core-format-text></h2>
|
<h2>{{ 'addon.calendar.eventname' | translate }}</h2>
|
||||||
|
<p><core-format-text [text]="event.name"></core-format-text></p>
|
||||||
<ion-note item-end *ngIf="event.deleted">
|
<ion-note item-end *ngIf="event.deleted">
|
||||||
<ion-icon name="trash"></ion-icon> {{ 'core.deletedoffline' | translate }}
|
<ion-icon name="trash"></ion-icon> {{ 'core.deletedoffline' | translate }}
|
||||||
</ion-note>
|
</ion-note>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item text-wrap>
|
<ion-item>
|
||||||
<h2>{{ 'addon.calendar.eventstarttime' | translate}}</h2>
|
<h2>{{ 'addon.calendar.when' | translate }}</h2>
|
||||||
<p>{{ event.timestart * 1000 | coreFormatDate }}</p>
|
<p><core-format-text [text]="event.formattedtime"></core-format-text></p>
|
||||||
|
<ion-note item-end *ngIf="!isSplitViewOn && event.deleted">
|
||||||
|
<ion-icon name="trash"></ion-icon> {{ 'core.deletedoffline' | translate }}
|
||||||
|
</ion-note>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item text-wrap *ngIf="event.timeduration > 0">
|
<ion-item>
|
||||||
<h2>{{ 'addon.calendar.eventendtime' | translate}}</h2>
|
<h2>{{ 'addon.calendar.eventtype' | translate }}</h2>
|
||||||
<p>{{ (event.timestart + event.timeduration) * 1000 | coreFormatDate }}</p>
|
<p>{{ 'addon.calendar.type' + event.formattedType | translate }}</p>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<a ion-item text-wrap *ngIf="courseName" [href]="courseUrl" core-link capture="true">
|
<a ion-item text-wrap *ngIf="courseName" [href]="courseUrl" core-link capture="true">
|
||||||
<h2>{{ 'core.course' | translate}}</h2>
|
<h2>{{ 'core.course' | translate}}</h2>
|
||||||
|
@ -53,10 +62,8 @@
|
||||||
<h2>{{ 'core.category' | translate}}</h2>
|
<h2>{{ 'core.category' | translate}}</h2>
|
||||||
<p><core-format-text [text]="categoryPath"></core-format-text></p>
|
<p><core-format-text [text]="categoryPath"></core-format-text></p>
|
||||||
</a>
|
</a>
|
||||||
<ion-item text-wrap *ngIf="event.moduleIcon">
|
|
||||||
<img *ngIf="event.moduleIcon" src="{{event.moduleIcon}}" item-start alt="" role="presentation" class="core-module-icon"> {{event.moduleName}}
|
|
||||||
</ion-item>
|
|
||||||
<ion-item text-wrap *ngIf="event.description">
|
<ion-item text-wrap *ngIf="event.description">
|
||||||
|
<h2>{{ 'core.description' | translate}}</h2>
|
||||||
<p>
|
<p>
|
||||||
<core-format-text [text]="event.description"></core-format-text>
|
<core-format-text [text]="event.description"></core-format-text>
|
||||||
</p>
|
</p>
|
||||||
|
|
|
@ -57,7 +57,6 @@ export class AddonCalendarEventPage implements OnDestroy {
|
||||||
notificationMax: string;
|
notificationMax: string;
|
||||||
notificationTimeText: string;
|
notificationTimeText: string;
|
||||||
event: any = {};
|
event: any = {};
|
||||||
title: string;
|
|
||||||
courseName: string;
|
courseName: string;
|
||||||
groupName: string;
|
groupName: string;
|
||||||
courseUrl = '';
|
courseUrl = '';
|
||||||
|
@ -236,8 +235,6 @@ export class AddonCalendarEventPage implements OnDestroy {
|
||||||
this.courseUrl = '';
|
this.courseUrl = '';
|
||||||
this.moduleUrl = '';
|
this.moduleUrl = '';
|
||||||
|
|
||||||
// Guess event title.
|
|
||||||
let title = this.translate.instant('addon.calendar.type' + event.eventtype);
|
|
||||||
if (event.moduleIcon) {
|
if (event.moduleIcon) {
|
||||||
// It's a module event, translate the module name to the current language.
|
// It's a module event, translate the module name to the current language.
|
||||||
const name = this.courseProvider.translateModuleName(event.modulename);
|
const name = this.courseProvider.translateModuleName(event.modulename);
|
||||||
|
@ -245,27 +242,12 @@ export class AddonCalendarEventPage implements OnDestroy {
|
||||||
event.moduleName = name;
|
event.moduleName = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate the title of the page;
|
|
||||||
if (title == 'addon.calendar.type' + event.eventtype) {
|
|
||||||
title = this.translate.instant('core.mod_' + event.modulename + '.' + event.eventtype);
|
|
||||||
|
|
||||||
if (title == 'core.mod_' + event.modulename + '.' + event.eventtype) {
|
|
||||||
title = name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the module URL.
|
// Get the module URL.
|
||||||
if (canGetById) {
|
if (canGetById) {
|
||||||
this.moduleUrl = event.url;
|
this.moduleUrl = event.url;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
if (title == 'addon.calendar.type' + event.eventtype) {
|
|
||||||
title = event.name;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.title = title;
|
|
||||||
|
|
||||||
// If the event belongs to a course, get the course name and the URL to view it.
|
// If the event belongs to a course, get the course name and the URL to view it.
|
||||||
if (canGetById && event.course && event.course.id != this.siteHomeId) {
|
if (canGetById && event.course && event.course.id != this.siteHomeId) {
|
||||||
this.courseName = event.course.fullname;
|
this.courseName = event.course.fullname;
|
||||||
|
@ -311,6 +293,13 @@ export class AddonCalendarEventPage implements OnDestroy {
|
||||||
event.deleted = deleted;
|
event.deleted = deleted;
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
// Re-calculate the formatted time so it uses the device date.
|
||||||
|
promises.push(this.calendarProvider.getCalendarTimeFormat().then((timeFormat) => {
|
||||||
|
this.calendarProvider.formatEventTime(event, timeFormat).then((time) => {
|
||||||
|
event.formattedtime = time;
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
return Promise.all(promises);
|
return Promise.all(promises);
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
this.domUtils.showErrorModalDefault(error, 'addon.calendar.errorloadevent', true);
|
this.domUtils.showErrorModalDefault(error, 'addon.calendar.errorloadevent', true);
|
||||||
|
@ -396,7 +385,12 @@ export class AddonCalendarEventPage implements OnDestroy {
|
||||||
refreshEvent(sync?: boolean, showErrors?: boolean): Promise<any> {
|
refreshEvent(sync?: boolean, showErrors?: boolean): Promise<any> {
|
||||||
this.syncIcon = 'spinner';
|
this.syncIcon = 'spinner';
|
||||||
|
|
||||||
return this.calendarProvider.invalidateEvent(this.eventId).catch(() => {
|
const promises = [];
|
||||||
|
|
||||||
|
promises.push(this.calendarProvider.invalidateEvent(this.eventId));
|
||||||
|
promises.push(this.calendarProvider.invalidateTimeFormat());
|
||||||
|
|
||||||
|
return Promise.all(promises).catch(() => {
|
||||||
// Ignore errors.
|
// Ignore errors.
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
return this.fetchEvent(sync, showErrors);
|
return this.fetchEvent(sync, showErrors);
|
||||||
|
@ -493,7 +487,6 @@ export class AddonCalendarEventPage implements OnDestroy {
|
||||||
eventId: this.eventId
|
eventId: this.eventId
|
||||||
}, this.sitesProvider.getCurrentSiteId());
|
}, this.sitesProvider.getCurrentSiteId());
|
||||||
|
|
||||||
this.domUtils.showToast('addon.calendar.eventcalendareventdeleted', true, 3000, undefined, false);
|
|
||||||
this.event.deleted = false;
|
this.event.deleted = false;
|
||||||
|
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
<ion-header>
|
||||||
|
<ion-navbar core-back-button>
|
||||||
|
<ion-title>{{ 'addon.calendar.calendarevents' | translate }}</ion-title>
|
||||||
|
<ion-buttons end>
|
||||||
|
<button *ngIf="showCalendar" ion-button icon-only (click)="toggleDisplay()" [attr.aria-label]="'addon.calendar.upcomingevents' | translate">
|
||||||
|
<ion-icon name="list"></ion-icon>
|
||||||
|
</button>
|
||||||
|
<button *ngIf="!showCalendar" ion-button icon-only (click)="toggleDisplay()" [attr.aria-label]="'addon.calendar.monthlyview' | translate">
|
||||||
|
<core-icon name="fa-calendar-o"></core-icon>
|
||||||
|
</button>
|
||||||
|
<button *ngIf="courses && courses.length" ion-button icon-only (click)="openCourseFilter($event)" [attr.aria-label]="'core.courses.filter' | translate">
|
||||||
|
<ion-icon name="funnel"></ion-icon>
|
||||||
|
</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>
|
||||||
|
</ion-header>
|
||||||
|
<ion-content>
|
||||||
|
<ion-refresher [enabled]="loaded" (ionRefresh)="doRefresh($event)">
|
||||||
|
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||||
|
</ion-refresher>
|
||||||
|
|
||||||
|
<!-- 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 [hidden]="!showCalendar" [initialYear]="year" [initialMonth]="month" [courseId]="courseId" [categoryId]="categoryId" [displayNavButtons]="showCalendar" (onEventClicked)="gotoEvent($event)" (onDayClicked)="gotoDay($event)"></addon-calendar-calendar>
|
||||||
|
|
||||||
|
<addon-calendar-upcoming-events *ngIf="loadUpcoming" [hidden]="showCalendar" [courseId]="courseId" [categoryId]="categoryId" (onEventClicked)="gotoEvent($event)"></addon-calendar-upcoming-events>
|
||||||
|
|
||||||
|
<!-- Create a calendar event. -->
|
||||||
|
<ion-fab core-fab bottom end *ngIf="canCreate">
|
||||||
|
<button ion-fab (click)="openEdit()" [attr.aria-label]="'addon.calendar.newevent' | translate">
|
||||||
|
<ion-icon name="add"></ion-icon>
|
||||||
|
</button>
|
||||||
|
</ion-fab>
|
||||||
|
</ion-content>
|
|
@ -0,0 +1,37 @@
|
||||||
|
// (C) Copyright 2015 Martin Dougiamas
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { IonicPageModule } from 'ionic-angular';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { CoreComponentsModule } from '@components/components.module';
|
||||||
|
import { CoreDirectivesModule } from '@directives/directives.module';
|
||||||
|
import { CorePipesModule } from '@pipes/pipes.module';
|
||||||
|
import { AddonCalendarComponentsModule } from '../../components/components.module';
|
||||||
|
import { AddonCalendarIndexPage } from './index';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
AddonCalendarIndexPage,
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
CoreComponentsModule,
|
||||||
|
CoreDirectivesModule,
|
||||||
|
CorePipesModule,
|
||||||
|
AddonCalendarComponentsModule,
|
||||||
|
IonicPageModule.forChild(AddonCalendarIndexPage),
|
||||||
|
TranslateModule.forChild()
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class AddonCalendarIndexPageModule {}
|
|
@ -0,0 +1,378 @@
|
||||||
|
// (C) Copyright 2015 Martin Dougiamas
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OFx ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { Component, OnInit, OnDestroy, ViewChild, NgZone } from '@angular/core';
|
||||||
|
import { IonicPage, NavParams, NavController } 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 { AddonCalendarUpcomingEventsComponent } from '../../components/upcoming-events/upcoming-events';
|
||||||
|
import { AddonCalendarSyncProvider } from '../../providers/calendar-sync';
|
||||||
|
import { CoreCoursesHelperProvider } from '@core/courses/providers/helper';
|
||||||
|
import { Network } from '@ionic-native/network';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Page that displays the calendar events.
|
||||||
|
*/
|
||||||
|
@IonicPage({ segment: 'addon-calendar-index' })
|
||||||
|
@Component({
|
||||||
|
selector: 'page-addon-calendar-index',
|
||||||
|
templateUrl: 'index.html',
|
||||||
|
})
|
||||||
|
export class AddonCalendarIndexPage implements OnInit, OnDestroy {
|
||||||
|
@ViewChild(AddonCalendarCalendarComponent) calendarComponent: AddonCalendarCalendarComponent;
|
||||||
|
@ViewChild(AddonCalendarUpcomingEventsComponent) upcomingEventsComponent: AddonCalendarUpcomingEventsComponent;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
year: number;
|
||||||
|
month: number;
|
||||||
|
courseId: number;
|
||||||
|
categoryId: number;
|
||||||
|
canCreate = false;
|
||||||
|
courses: any[];
|
||||||
|
notificationsEnabled = false;
|
||||||
|
loaded = false;
|
||||||
|
hasOffline = false;
|
||||||
|
isOnline = false;
|
||||||
|
syncIcon: string;
|
||||||
|
showCalendar = true;
|
||||||
|
loadUpcoming = false;
|
||||||
|
|
||||||
|
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 eventsProvider: CoreEventsProvider,
|
||||||
|
private coursesHelper: CoreCoursesHelperProvider,
|
||||||
|
private appProvider: CoreAppProvider) {
|
||||||
|
|
||||||
|
this.courseId = navParams.get('courseId');
|
||||||
|
this.eventId = navParams.get('eventId') || false;
|
||||||
|
this.year = navParams.get('year');
|
||||||
|
this.month = navParams.get('month');
|
||||||
|
this.notificationsEnabled = localNotificationsProvider.isAvailable();
|
||||||
|
this.currentSiteId = sitesProvider.getCurrentSiteId();
|
||||||
|
this.loadUpcoming = !!navParams.get('upcoming');
|
||||||
|
this.showCalendar = !this.loadUpcoming;
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
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(sync?: boolean, showErrors?: boolean): Promise<any> {
|
||||||
|
|
||||||
|
this.syncIcon = 'spinner';
|
||||||
|
this.isOnline = this.appProvider.isOnline();
|
||||||
|
|
||||||
|
let promise;
|
||||||
|
|
||||||
|
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]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.updated) {
|
||||||
|
// Trigger a manual sync event.
|
||||||
|
result.source = 'index';
|
||||||
|
|
||||||
|
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.coursesHelper.getCoursesForPopover(this.courseId).then((data) => {
|
||||||
|
this.courses = data.courses;
|
||||||
|
this.categoryId = data.categoryId;
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 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';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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, 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());
|
||||||
|
|
||||||
|
// Refresh the sub-component.
|
||||||
|
if (this.showCalendar && this.calendarComponent) {
|
||||||
|
promises.push(this.calendarComponent.refreshData());
|
||||||
|
} else if (!this.showCalendar && this.upcomingEventsComponent) {
|
||||||
|
promises.push(this.upcomingEventsComponent.refreshData());
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View a certain day.
|
||||||
|
*
|
||||||
|
* @param {any} data Data with the year, month and day.
|
||||||
|
*/
|
||||||
|
gotoDay(data: any): void {
|
||||||
|
const params: any = {
|
||||||
|
day: data.day,
|
||||||
|
month: data.month,
|
||||||
|
year: data.year
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.courseId) {
|
||||||
|
params.courseId = this.courseId;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.navCtrl.push('AddonCalendarDayPage', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the context menu.
|
||||||
|
*
|
||||||
|
* @param {MouseEvent} event Event.
|
||||||
|
*/
|
||||||
|
openCourseFilter(event: MouseEvent): void {
|
||||||
|
this.coursesHelper.selectCourse(event, this.courses, this.courseId).then((result) => {
|
||||||
|
if (typeof result.courseId != 'undefined') {
|
||||||
|
this.courseId = result.courseId > 0 ? result.courseId : undefined;
|
||||||
|
this.categoryId = result.courseId > 0 ? result.categoryId : undefined;
|
||||||
|
|
||||||
|
// Course viewed has changed, check if the user can create events for this course calendar.
|
||||||
|
this.calendarHelper.canEditEvents(this.courseId).then((canEdit) => {
|
||||||
|
this.canCreate = canEdit;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open page to create/edit an event.
|
||||||
|
*
|
||||||
|
* @param {number} [eventId] Event ID to edit.
|
||||||
|
*/
|
||||||
|
openEdit(eventId?: number): void {
|
||||||
|
const params: any = {};
|
||||||
|
|
||||||
|
if (eventId) {
|
||||||
|
params.eventId = eventId;
|
||||||
|
}
|
||||||
|
if (this.courseId) {
|
||||||
|
params.courseId = this.courseId;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.navCtrl.push('AddonCalendarEditEventPage', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open calendar events settings.
|
||||||
|
*/
|
||||||
|
openSettings(): void {
|
||||||
|
this.navCtrl.push('AddonCalendarSettingsPage');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toogle display: monthly view or upcoming events.
|
||||||
|
*/
|
||||||
|
toggleDisplay(): void {
|
||||||
|
this.showCalendar = !this.showCalendar;
|
||||||
|
|
||||||
|
if (!this.showCalendar) {
|
||||||
|
this.loadUpcoming = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,19 +13,18 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { Component, ViewChild, OnDestroy, NgZone } from '@angular/core';
|
import { Component, ViewChild, OnDestroy, NgZone } from '@angular/core';
|
||||||
import { IonicPage, Content, PopoverController, NavParams, NavController } from 'ionic-angular';
|
import { IonicPage, Content, NavParams, NavController } from 'ionic-angular';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
|
||||||
import { AddonCalendarProvider } from '../../providers/calendar';
|
import { AddonCalendarProvider } from '../../providers/calendar';
|
||||||
import { AddonCalendarOfflineProvider } from '../../providers/calendar-offline';
|
import { AddonCalendarOfflineProvider } from '../../providers/calendar-offline';
|
||||||
import { AddonCalendarHelperProvider } from '../../providers/helper';
|
import { AddonCalendarHelperProvider } from '../../providers/helper';
|
||||||
import { AddonCalendarSyncProvider } from '../../providers/calendar-sync';
|
import { AddonCalendarSyncProvider } from '../../providers/calendar-sync';
|
||||||
import { CoreCoursesProvider } from '@core/courses/providers/courses';
|
import { CoreCoursesProvider } from '@core/courses/providers/courses';
|
||||||
|
import { CoreCoursesHelperProvider } from '@core/courses/providers/helper';
|
||||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||||
import { CoreTimeUtilsProvider } from '@providers/utils/time';
|
import { CoreTimeUtilsProvider } from '@providers/utils/time';
|
||||||
import { CoreUtilsProvider } from '@providers/utils/utils';
|
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||||
import { CoreSitesProvider } from '@providers/sites';
|
import { CoreSitesProvider } from '@providers/sites';
|
||||||
import { CoreLocalNotificationsProvider } from '@providers/local-notifications';
|
import { CoreLocalNotificationsProvider } from '@providers/local-notifications';
|
||||||
import { CoreCoursePickerMenuPopoverComponent } from '@components/course-picker-menu/course-picker-menu-popover';
|
|
||||||
import { CoreEventsProvider } from '@providers/events';
|
import { CoreEventsProvider } from '@providers/events';
|
||||||
import { CoreAppProvider } from '@providers/app';
|
import { CoreAppProvider } from '@providers/app';
|
||||||
import { CoreSplitViewComponent } from '@components/split-view/split-view';
|
import { CoreSplitViewComponent } from '@components/split-view/split-view';
|
||||||
|
@ -50,16 +49,10 @@ export class AddonCalendarListPage implements OnDestroy {
|
||||||
protected emptyEventsTimes = 0; // Variable to identify consecutive calls returning 0 events.
|
protected emptyEventsTimes = 0; // Variable to identify consecutive calls returning 0 events.
|
||||||
protected categoriesRetrieved = false;
|
protected categoriesRetrieved = false;
|
||||||
protected getCategories = false;
|
protected getCategories = false;
|
||||||
protected allCourses = {
|
|
||||||
id: -1,
|
|
||||||
fullname: this.translate.instant('core.fulllistofcourses'),
|
|
||||||
category: -1
|
|
||||||
};
|
|
||||||
protected categories = {};
|
protected categories = {};
|
||||||
protected siteHomeId: number;
|
protected siteHomeId: number;
|
||||||
protected obsDefaultTimeChange: any;
|
protected obsDefaultTimeChange: any;
|
||||||
protected eventId: number;
|
protected eventId: number;
|
||||||
protected preSelectedCourseId: number;
|
|
||||||
protected newEventObserver: any;
|
protected newEventObserver: any;
|
||||||
protected discardedObserver: any;
|
protected discardedObserver: any;
|
||||||
protected editEventObserver: any;
|
protected editEventObserver: any;
|
||||||
|
@ -80,18 +73,17 @@ export class AddonCalendarListPage implements OnDestroy {
|
||||||
filteredEvents = [];
|
filteredEvents = [];
|
||||||
canLoadMore = false;
|
canLoadMore = false;
|
||||||
loadMoreError = false;
|
loadMoreError = false;
|
||||||
filter = {
|
courseId: number;
|
||||||
course: this.allCourses
|
categoryId: number;
|
||||||
};
|
|
||||||
canCreate = false;
|
canCreate = false;
|
||||||
hasOffline = false;
|
hasOffline = false;
|
||||||
isOnline = false;
|
isOnline = false;
|
||||||
syncIcon: string; // Sync icon.
|
syncIcon: string; // Sync icon.
|
||||||
|
|
||||||
constructor(private translate: TranslateService, private calendarProvider: AddonCalendarProvider, navParams: NavParams,
|
constructor(private calendarProvider: AddonCalendarProvider, navParams: NavParams,
|
||||||
private domUtils: CoreDomUtilsProvider, private coursesProvider: CoreCoursesProvider, private utils: CoreUtilsProvider,
|
private domUtils: CoreDomUtilsProvider, private coursesProvider: CoreCoursesProvider, private utils: CoreUtilsProvider,
|
||||||
private calendarHelper: AddonCalendarHelperProvider, sitesProvider: CoreSitesProvider, zone: NgZone,
|
private calendarHelper: AddonCalendarHelperProvider, sitesProvider: CoreSitesProvider, zone: NgZone,
|
||||||
localNotificationsProvider: CoreLocalNotificationsProvider, private popoverCtrl: PopoverController,
|
localNotificationsProvider: CoreLocalNotificationsProvider, private coursesHelper: CoreCoursesHelperProvider,
|
||||||
private eventsProvider: CoreEventsProvider, private navCtrl: NavController, private appProvider: CoreAppProvider,
|
private eventsProvider: CoreEventsProvider, private navCtrl: NavController, private appProvider: CoreAppProvider,
|
||||||
private calendarOffline: AddonCalendarOfflineProvider, private calendarSync: AddonCalendarSyncProvider,
|
private calendarOffline: AddonCalendarOfflineProvider, private calendarSync: AddonCalendarSyncProvider,
|
||||||
network: Network, private timeUtils: CoreTimeUtilsProvider) {
|
network: Network, private timeUtils: CoreTimeUtilsProvider) {
|
||||||
|
@ -108,7 +100,7 @@ export class AddonCalendarListPage implements OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.eventId = navParams.get('eventId') || false;
|
this.eventId = navParams.get('eventId') || false;
|
||||||
this.preSelectedCourseId = navParams.get('courseId') || null;
|
this.courseId = navParams.get('courseId');
|
||||||
|
|
||||||
// Listen for events added. When an event is added, reload the data.
|
// Listen for events added. When an event is added, reload the data.
|
||||||
this.newEventObserver = eventsProvider.on(AddonCalendarProvider.NEW_EVENT_EVENT, (data) => {
|
this.newEventObserver = eventsProvider.on(AddonCalendarProvider.NEW_EVENT_EVENT, (data) => {
|
||||||
|
@ -177,6 +169,7 @@ export class AddonCalendarListPage implements OnDestroy {
|
||||||
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.markAsDeleted(data.eventId, true);
|
this.markAsDeleted(data.eventId, true);
|
||||||
|
this.deletedEvents.push(data.eventId);
|
||||||
this.hasOffline = true;
|
this.hasOffline = true;
|
||||||
} else {
|
} else {
|
||||||
// Event deleted, clear the details if needed and refresh the view.
|
// Event deleted, clear the details if needed and refresh the view.
|
||||||
|
@ -278,25 +271,17 @@ export class AddonCalendarListPage implements OnDestroy {
|
||||||
return promise.then(() => {
|
return promise.then(() => {
|
||||||
|
|
||||||
const promises = [];
|
const promises = [];
|
||||||
const courseId = this.filter.course.id != this.allCourses.id ? this.filter.course.id : undefined;
|
|
||||||
|
|
||||||
this.hasOffline = false;
|
this.hasOffline = false;
|
||||||
|
|
||||||
promises.push(this.calendarHelper.canEditEvents(courseId).then((canEdit) => {
|
promises.push(this.calendarHelper.canEditEvents(this.courseId).then((canEdit) => {
|
||||||
this.canCreate = canEdit;
|
this.canCreate = canEdit;
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Load courses for the popover.
|
// Load courses for the popover.
|
||||||
promises.push(this.coursesProvider.getUserCourses(false).then((courses) => {
|
promises.push(this.coursesHelper.getCoursesForPopover(this.courseId).then((result) => {
|
||||||
// Add "All courses".
|
this.courses = result.courses;
|
||||||
courses.unshift(this.allCourses);
|
this.categoryId = result.categoryId;
|
||||||
this.courses = courses;
|
|
||||||
|
|
||||||
if (this.preSelectedCourseId) {
|
|
||||||
this.filter.course = courses.find((course) => {
|
|
||||||
return course.id == this.preSelectedCourseId;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.fetchEvents(refresh);
|
return this.fetchEvents(refresh);
|
||||||
}));
|
}));
|
||||||
|
@ -418,55 +403,14 @@ export class AddonCalendarListPage implements OnDestroy {
|
||||||
* @return {any[]} Filtered events.
|
* @return {any[]} Filtered events.
|
||||||
*/
|
*/
|
||||||
protected getFilteredEvents(): any[] {
|
protected getFilteredEvents(): any[] {
|
||||||
if (this.filter.course.id == -1) {
|
if (!this.courseId) {
|
||||||
// No filter, display everything.
|
// No filter, display everything.
|
||||||
return this.events;
|
return this.events;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.events.filter(this.shouldDisplayEvent.bind(this));
|
return this.events.filter((event) => {
|
||||||
}
|
return this.calendarHelper.shouldDisplayEvent(event, this.courseId, this.categoryId, this.categories);
|
||||||
|
});
|
||||||
/**
|
|
||||||
* Check if an event should be displayed based on the filter.
|
|
||||||
*
|
|
||||||
* @param {any} event Event object.
|
|
||||||
* @return {boolean} Whether it should be displayed.
|
|
||||||
*/
|
|
||||||
protected shouldDisplayEvent(event: any): boolean {
|
|
||||||
if (event.eventtype == 'user' || event.eventtype == 'site') {
|
|
||||||
// User or site event, display it.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.eventtype == 'category') {
|
|
||||||
if (!event.categoryid || !Object.keys(this.categories).length) {
|
|
||||||
// We can't tell if the course belongs to the category, display them all.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (event.categoryid == this.filter.course.category) {
|
|
||||||
// The event is in the same category as the course, display it.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check parent categories.
|
|
||||||
let category = this.categories[this.filter.course.category];
|
|
||||||
while (category) {
|
|
||||||
if (!category.parent) {
|
|
||||||
// Category doesn't have parent, stop.
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.categoryid == category.parent) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
category = this.categories[category.parent];
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show the event if it is from site home or if it matches the selected course.
|
|
||||||
return event.courseid === this.siteHomeId || event.courseid == this.filter.course.id;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -653,28 +597,21 @@ export class AddonCalendarListPage implements OnDestroy {
|
||||||
* @param {MouseEvent} event Event.
|
* @param {MouseEvent} event Event.
|
||||||
*/
|
*/
|
||||||
openCourseFilter(event: MouseEvent): void {
|
openCourseFilter(event: MouseEvent): void {
|
||||||
const popover = this.popoverCtrl.create(CoreCoursePickerMenuPopoverComponent, {
|
this.coursesHelper.selectCourse(event, this.courses, this.courseId).then((result) => {
|
||||||
courses: this.courses,
|
if (typeof result.courseId != 'undefined') {
|
||||||
courseId: this.filter.course.id
|
this.courseId = result.courseId > 0 ? result.courseId : undefined;
|
||||||
});
|
this.categoryId = result.courseId > 0 ? result.categoryId : undefined;
|
||||||
popover.onDidDismiss((course) => {
|
|
||||||
if (course) {
|
// Course viewed has changed, check if the user can create events for this course calendar.
|
||||||
this.filter.course = course;
|
this.calendarHelper.canEditEvents(this.courseId).then((canEdit) => {
|
||||||
this.domUtils.scrollToTop(this.content);
|
this.canCreate = canEdit;
|
||||||
|
});
|
||||||
|
|
||||||
this.filteredEvents = this.getFilteredEvents();
|
this.filteredEvents = this.getFilteredEvents();
|
||||||
|
|
||||||
// Course viewed has changed, check if the user can create events for this course calendar.
|
this.domUtils.scrollToTop(this.content);
|
||||||
const courseId = this.filter.course.id != this.allCourses.id ? this.filter.course.id : undefined;
|
|
||||||
|
|
||||||
this.calendarHelper.canEditEvents(courseId).then((canEdit) => {
|
|
||||||
this.canCreate = canEdit;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
popover.present({
|
|
||||||
ev: event
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -690,8 +627,8 @@ export class AddonCalendarListPage implements OnDestroy {
|
||||||
if (eventId) {
|
if (eventId) {
|
||||||
params.eventId = eventId;
|
params.eventId = eventId;
|
||||||
}
|
}
|
||||||
if (this.filter.course.id != this.allCourses.id) {
|
if (this.courseId) {
|
||||||
params.courseId = this.filter.course.id;
|
params.courseId = this.courseId;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.splitviewCtrl.push('AddonCalendarEditEventPage', params);
|
this.splitviewCtrl.push('AddonCalendarEditEventPage', params);
|
||||||
|
|
|
@ -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.
|
* Check if an event is deleted.
|
||||||
*
|
*
|
||||||
|
|
|
@ -18,7 +18,9 @@ import { CoreSitesProvider, CoreSiteSchema } from '@providers/sites';
|
||||||
import { CoreSite } from '@classes/site';
|
import { CoreSite } from '@classes/site';
|
||||||
import { CoreCoursesProvider } from '@core/courses/providers/courses';
|
import { CoreCoursesProvider } from '@core/courses/providers/courses';
|
||||||
import { CoreAppProvider } from '@providers/app';
|
import { CoreAppProvider } from '@providers/app';
|
||||||
|
import { CoreTextUtilsProvider } from '@providers/utils/text';
|
||||||
import { CoreTimeUtilsProvider } from '@providers/utils/time';
|
import { CoreTimeUtilsProvider } from '@providers/utils/time';
|
||||||
|
import { CoreUrlUtilsProvider } from '@providers/utils/url';
|
||||||
import { CoreUtilsProvider } from '@providers/utils/utils';
|
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||||
import { CoreGroupsProvider } from '@providers/groups';
|
import { CoreGroupsProvider } from '@providers/groups';
|
||||||
import { CoreConstants } from '@core/constants';
|
import { CoreConstants } from '@core/constants';
|
||||||
|
@ -27,7 +29,9 @@ import { CoreConfigProvider } from '@providers/config';
|
||||||
import { ILocalNotification } from '@ionic-native/local-notifications';
|
import { ILocalNotification } from '@ionic-native/local-notifications';
|
||||||
import { SQLiteDB } from '@classes/sqlitedb';
|
import { SQLiteDB } from '@classes/sqlitedb';
|
||||||
import { AddonCalendarOfflineProvider } from './calendar-offline';
|
import { AddonCalendarOfflineProvider } from './calendar-offline';
|
||||||
|
import { CoreUserProvider } from '@core/user/providers/user';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import * as moment from 'moment';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service to handle calendar events.
|
* Service to handle calendar events.
|
||||||
|
@ -49,8 +53,43 @@ export class AddonCalendarProvider {
|
||||||
static TYPE_GROUP = 'group';
|
static TYPE_GROUP = 'group';
|
||||||
static TYPE_SITE = 'site';
|
static TYPE_SITE = 'site';
|
||||||
static TYPE_USER = 'user';
|
static TYPE_USER = 'user';
|
||||||
|
|
||||||
|
static CALENDAR_TF_24 = '%H:%M'; // Calendar time in 24 hours format.
|
||||||
|
static CALENDAR_TF_12 = '%I:%M %p'; // Calendar time in 12 hours format.
|
||||||
|
|
||||||
protected ROOT_CACHE_KEY = 'mmaCalendar:';
|
protected ROOT_CACHE_KEY = 'mmaCalendar:';
|
||||||
|
|
||||||
|
protected weekDays = [
|
||||||
|
{
|
||||||
|
shortname: 'addon.calendar.sun',
|
||||||
|
fullname: 'addon.calendar.sunday'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
shortname: 'addon.calendar.mon',
|
||||||
|
fullname: 'addon.calendar.monday'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
shortname: 'addon.calendar.tue',
|
||||||
|
fullname: 'addon.calendar.tuesday'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
shortname: 'addon.calendar.wed',
|
||||||
|
fullname: 'addon.calendar.wednesday'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
shortname: 'addon.calendar.thu',
|
||||||
|
fullname: 'addon.calendar.thursday'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
shortname: 'addon.calendar.fri',
|
||||||
|
fullname: 'addon.calendar.friday'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
shortname: 'addon.calendar.sat',
|
||||||
|
fullname: 'addon.calendar.saturday'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
// Variables for database.
|
// Variables for database.
|
||||||
static EVENTS_TABLE = 'addon_calendar_events_2';
|
static EVENTS_TABLE = 'addon_calendar_events_2';
|
||||||
static REMINDERS_TABLE = 'addon_calendar_reminders';
|
static REMINDERS_TABLE = 'addon_calendar_reminders';
|
||||||
|
@ -218,11 +257,21 @@ export class AddonCalendarProvider {
|
||||||
|
|
||||||
protected logger;
|
protected logger;
|
||||||
|
|
||||||
constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private groupsProvider: CoreGroupsProvider,
|
constructor(logger: CoreLoggerProvider,
|
||||||
private coursesProvider: CoreCoursesProvider, private timeUtils: CoreTimeUtilsProvider,
|
private sitesProvider: CoreSitesProvider,
|
||||||
private localNotificationsProvider: CoreLocalNotificationsProvider, private configProvider: CoreConfigProvider,
|
private groupsProvider: CoreGroupsProvider,
|
||||||
private utils: CoreUtilsProvider, private calendarOffline: AddonCalendarOfflineProvider,
|
private coursesProvider: CoreCoursesProvider,
|
||||||
private appProvider: CoreAppProvider, private translate: TranslateService) {
|
private textUtils: CoreTextUtilsProvider,
|
||||||
|
private timeUtils: CoreTimeUtilsProvider,
|
||||||
|
private urlUtils: CoreUrlUtilsProvider,
|
||||||
|
private localNotificationsProvider: CoreLocalNotificationsProvider,
|
||||||
|
private configProvider: CoreConfigProvider,
|
||||||
|
private utils: CoreUtilsProvider,
|
||||||
|
private calendarOffline: AddonCalendarOfflineProvider,
|
||||||
|
private appProvider: CoreAppProvider,
|
||||||
|
private translate: TranslateService,
|
||||||
|
private userProvider: CoreUserProvider) {
|
||||||
|
|
||||||
this.logger = logger.getInstance('AddonCalendarProvider');
|
this.logger = logger.getInstance('AddonCalendarProvider');
|
||||||
this.sitesProvider.registerSiteSchema(this.siteSchema);
|
this.sitesProvider.registerSiteSchema(this.siteSchema);
|
||||||
}
|
}
|
||||||
|
@ -237,6 +286,8 @@ export class AddonCalendarProvider {
|
||||||
canDeleteEvents(siteId?: string): Promise<boolean> {
|
canDeleteEvents(siteId?: string): Promise<boolean> {
|
||||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
return this.canDeleteEventsInSite(site);
|
return this.canDeleteEventsInSite(site);
|
||||||
|
}).catch(() => {
|
||||||
|
return false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -263,6 +314,8 @@ export class AddonCalendarProvider {
|
||||||
canEditEvents(siteId?: string): Promise<boolean> {
|
canEditEvents(siteId?: string): Promise<boolean> {
|
||||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
return this.canEditEventsInSite(site);
|
return this.canEditEventsInSite(site);
|
||||||
|
}).catch(() => {
|
||||||
|
return false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -280,6 +333,34 @@ export class AddonCalendarProvider {
|
||||||
return site.isVersionGreaterEqualThan('3.7.1');
|
return site.isVersionGreaterEqualThan('3.7.1');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a certain site allows viewing events in monthly view.
|
||||||
|
*
|
||||||
|
* @param {string} [siteId] Site Id. If not defined, use current site.
|
||||||
|
* @return {Promise<boolean>} Promise resolved with true if monthly view is supported.
|
||||||
|
* @since 3.4
|
||||||
|
*/
|
||||||
|
canViewMonth(siteId?: string): Promise<boolean> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
return this.canViewMonthInSite(site);
|
||||||
|
}).catch(() => {
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a certain site allows viewing events in monthly view.
|
||||||
|
*
|
||||||
|
* @param {CoreSite} [site] Site. If not defined, use current site.
|
||||||
|
* @return {boolean} Whether monthly view is supported.
|
||||||
|
* @since 3.4
|
||||||
|
*/
|
||||||
|
canViewMonthInSite(site?: CoreSite): boolean {
|
||||||
|
site = site || this.sitesProvider.getCurrentSite();
|
||||||
|
|
||||||
|
return site.wsAvailable('core_calendar_get_calendar_monthly_view');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes expired events from local DB.
|
* Removes expired events from local DB.
|
||||||
*
|
*
|
||||||
|
@ -393,6 +474,98 @@ export class AddonCalendarProvider {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if event ends the same day or not.
|
||||||
|
*
|
||||||
|
* @param {any} event Event info.
|
||||||
|
* @return {boolean} If the .
|
||||||
|
*/
|
||||||
|
endsSameDay(event: any): boolean {
|
||||||
|
if (!event.timeduration) {
|
||||||
|
// No duration.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if day has changed.
|
||||||
|
return moment(event.timestart * 1000).isSame((event.timestart + event.timeduration) * 1000, 'day');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format event time. Similar to calendar_format_event_time.
|
||||||
|
*
|
||||||
|
* @param {any} event Event to format.
|
||||||
|
* @param {string} format Calendar time format (from getCalendarTimeFormat).
|
||||||
|
* @param {boolean} [useCommonWords=true] Whether to use common words like "Today", "Yesterday", etc.
|
||||||
|
* @param {number} [seenDay] Timestamp of day currently seen. If set, the function will not add links to this day.
|
||||||
|
* @param {number} [showTime=0] Determine the show time GMT timestamp.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<string>} Promise resolved with the formatted event time.
|
||||||
|
*/
|
||||||
|
formatEventTime(event: any, format: string, useCommonWords: boolean = true, seenDay?: number, showTime: number = 0,
|
||||||
|
siteId?: string): Promise<string> {
|
||||||
|
|
||||||
|
const start = event.timestart * 1000,
|
||||||
|
end = (event.timestart + event.timeduration) * 1000;
|
||||||
|
let time;
|
||||||
|
|
||||||
|
if (event.timeduration) {
|
||||||
|
|
||||||
|
if (moment(start).isSame(end, 'day')) {
|
||||||
|
// Event starts and ends the same day.
|
||||||
|
if (event.timeduration == CoreConstants.SECONDS_DAY) {
|
||||||
|
time = this.translate.instant('addon.calendar.allday');
|
||||||
|
} else {
|
||||||
|
time = this.timeUtils.userDate(start, format) + ' <strong>»</strong> ' +
|
||||||
|
this.timeUtils.userDate(end, format);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Event lasts more than one day.
|
||||||
|
const timeStart = this.timeUtils.userDate(start, format),
|
||||||
|
timeEnd = this.timeUtils.userDate(end, format),
|
||||||
|
promises = [];
|
||||||
|
|
||||||
|
// Don't use common words when the event lasts more than one day.
|
||||||
|
let dayStart = this.getDayRepresentation(start, false) + ', ',
|
||||||
|
dayEnd = this.getDayRepresentation(end, false) + ', ';
|
||||||
|
|
||||||
|
// Add links to the days if needed.
|
||||||
|
if (dayStart && (!seenDay || !moment(seenDay).isSame(start, 'day'))) {
|
||||||
|
promises.push(this.getViewUrl('day', event.timestart, undefined, siteId).then((url) => {
|
||||||
|
dayStart = this.urlUtils.buildLink(url, dayStart);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
if (dayEnd && (!seenDay || !moment(seenDay).isSame(end, 'day'))) {
|
||||||
|
promises.push(this.getViewUrl('day', end / 1000, undefined, siteId).then((url) => {
|
||||||
|
dayEnd = this.urlUtils.buildLink(url, dayEnd);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.all(promises).then(() => {
|
||||||
|
return dayStart + timeStart + ' <strong>»</strong> ' + dayEnd + timeEnd;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// There is no time duration.
|
||||||
|
time = this.timeUtils.userDate(start, format);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!showTime) {
|
||||||
|
// Display day + time.
|
||||||
|
if (seenDay && moment(seenDay).isSame(start, 'day')) {
|
||||||
|
// This day is currently being displayed, don't add an link.
|
||||||
|
return Promise.resolve(this.getDayRepresentation(start, useCommonWords) + ', ' + time);
|
||||||
|
} else {
|
||||||
|
// Add link to view the day.
|
||||||
|
return this.getViewUrl('day', event.timestart, undefined, siteId).then((url) => {
|
||||||
|
return this.urlUtils.buildLink(url, this.getDayRepresentation(start, useCommonWords)) + ', ' + time;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Promise.resolve(time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get access information for a calendar (either course calendar or site calendar).
|
* Get access information for a calendar (either course calendar or site calendar).
|
||||||
*
|
*
|
||||||
|
@ -482,6 +655,84 @@ export class AddonCalendarProvider {
|
||||||
return this.ROOT_CACHE_KEY + 'allowedEventTypes:' + (courseId || 0);
|
return this.ROOT_CACHE_KEY + 'allowedEventTypes:' + (courseId || 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the "look ahead" for a certain user.
|
||||||
|
*
|
||||||
|
* @param {string} [siteId] ID of the site. If not defined, use current site.
|
||||||
|
* @return {Promise<number>} Promise resolved with the look ahead (number of days).
|
||||||
|
*/
|
||||||
|
getCalendarLookAhead(siteId?: string): Promise<number> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
return this.userProvider.getUserPreference('calendar_lookahead').catch((error) => {
|
||||||
|
// Ignore errors.
|
||||||
|
}).then((value): any => {
|
||||||
|
if (typeof value != 'undefined') {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return site.getStoredConfig('calendar_lookahead');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the time format to use in calendar.
|
||||||
|
*
|
||||||
|
* @param {string} [siteId] ID of the site. If not defined, use current site.
|
||||||
|
* @return {Promise<string>} Promise resolved with the format.
|
||||||
|
*/
|
||||||
|
getCalendarTimeFormat(siteId?: string): Promise<string> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
return this.userProvider.getUserPreference('calendar_timeformat').catch((error) => {
|
||||||
|
// Ignore errors.
|
||||||
|
}).then((format) => {
|
||||||
|
|
||||||
|
if (!format || format === '0') {
|
||||||
|
format = site.getStoredConfig('calendar_site_timeformat');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (format === AddonCalendarProvider.CALENDAR_TF_12) {
|
||||||
|
format = this.translate.instant('core.strftimetime12');
|
||||||
|
} else if (format === AddonCalendarProvider.CALENDAR_TF_24) {
|
||||||
|
format = this.translate.instant('core.strftimetime24');
|
||||||
|
}
|
||||||
|
|
||||||
|
return format && format !== '0' ? format : this.translate.instant('core.strftimetime');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the representation day. Equivalent to Moodle's calendar_day_representation.
|
||||||
|
*
|
||||||
|
* @param {number} time Timestamp to get the day from.
|
||||||
|
* @param {boolean} [useCommonWords=true] Whether to use common words like "Today", "Yesterday", etc.
|
||||||
|
* @return {string} The formatted date/time.
|
||||||
|
*/
|
||||||
|
getDayRepresentation(time: number, useCommonWords: boolean = true): string {
|
||||||
|
|
||||||
|
if (!useCommonWords) {
|
||||||
|
// We don't want words, just a date.
|
||||||
|
return this.timeUtils.userDate(time, 'core.strftimedayshort');
|
||||||
|
}
|
||||||
|
|
||||||
|
const date = moment(time),
|
||||||
|
today = moment();
|
||||||
|
|
||||||
|
if (date.isSame(today, 'day')) {
|
||||||
|
return this.translate.instant('addon.calendar.today');
|
||||||
|
|
||||||
|
} else if (date.isSame(today.clone().subtract(1, 'days'), 'day')) {
|
||||||
|
return this.translate.instant('addon.calendar.yesterday');
|
||||||
|
|
||||||
|
} else if (date.isSame(today.clone().add(1, 'days'), 'day')) {
|
||||||
|
return this.translate.instant('addon.calendar.tomorrow');
|
||||||
|
|
||||||
|
} else {
|
||||||
|
return this.timeUtils.userDate(time, 'core.strftimedayshort');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the configured default notification time.
|
* Get the configured default notification time.
|
||||||
*
|
*
|
||||||
|
@ -602,6 +853,21 @@ export class AddonCalendarProvider {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the normalised event type.
|
||||||
|
* Activity events are normalised to be course events.
|
||||||
|
*
|
||||||
|
* @param {any} event The event to get its type.
|
||||||
|
* @return {string} Event type.
|
||||||
|
*/
|
||||||
|
getEventType(event: any): string {
|
||||||
|
if (event.modulename) {
|
||||||
|
return 'course';
|
||||||
|
}
|
||||||
|
|
||||||
|
return event.eventtype;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove an event reminder and cancel the notification.
|
* Remove an event reminder and cancel the notification.
|
||||||
*
|
*
|
||||||
|
@ -619,6 +885,79 @@ export class AddonCalendarProvider {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get calendar events for a certain day.
|
||||||
|
*
|
||||||
|
* @param {number} year Year to get.
|
||||||
|
* @param {number} month Month to get.
|
||||||
|
* @param {number} day Day to get.
|
||||||
|
* @param {number} [courseId] Course to get.
|
||||||
|
* @param {number} [categoryId] Category to get.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<any>} Promise resolved with the response.
|
||||||
|
*/
|
||||||
|
getDayEvents(year: number, month: number, day: number, courseId?: number, categoryId?: number, siteId?: string): Promise<any> {
|
||||||
|
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
|
||||||
|
const data: any = {
|
||||||
|
year: year,
|
||||||
|
month: month,
|
||||||
|
day: day
|
||||||
|
};
|
||||||
|
|
||||||
|
if (courseId) {
|
||||||
|
data.courseid = courseId;
|
||||||
|
}
|
||||||
|
if (categoryId) {
|
||||||
|
data.categoryid = categoryId;
|
||||||
|
}
|
||||||
|
|
||||||
|
const preSets = {
|
||||||
|
cacheKey: this.getDayEventsCacheKey(year, month, day, courseId, categoryId),
|
||||||
|
updateFrequency: CoreSite.FREQUENCY_SOMETIMES
|
||||||
|
};
|
||||||
|
|
||||||
|
return site.read('core_calendar_get_calendar_day_view', data, preSets);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get prefix cache key for day events WS calls.
|
||||||
|
*
|
||||||
|
* @return {string} Prefix Cache key.
|
||||||
|
*/
|
||||||
|
protected getDayEventsPrefixCacheKey(): string {
|
||||||
|
return this.ROOT_CACHE_KEY + 'day:';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get prefix cache key for a certain day for day events WS calls.
|
||||||
|
*
|
||||||
|
* @param {number} year Year to get.
|
||||||
|
* @param {number} month Month to get.
|
||||||
|
* @param {number} day Day to get.
|
||||||
|
* @return {string} Prefix Cache key.
|
||||||
|
*/
|
||||||
|
protected getDayEventsDayPrefixCacheKey(year: number, month: number, day: number): string {
|
||||||
|
return this.getDayEventsPrefixCacheKey() + year + ':' + month + ':' + day + ':';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get cache key for day events WS calls.
|
||||||
|
*
|
||||||
|
* @param {number} year Year to get.
|
||||||
|
* @param {number} month Month to get.
|
||||||
|
* @param {number} day Day to get.
|
||||||
|
* @param {number} [courseId] Course to get.
|
||||||
|
* @param {number} [categoryId] Category to get.
|
||||||
|
* @return {string} Cache key.
|
||||||
|
*/
|
||||||
|
protected getDayEventsCacheKey(year: number, month: number, day: number, courseId?: number, categoryId?: number): string {
|
||||||
|
return this.getDayEventsDayPrefixCacheKey(year, month, day) + (courseId ? courseId : '') + ':' +
|
||||||
|
(categoryId ? categoryId : '');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a calendar reminders from local Db.
|
* Get a calendar reminders from local Db.
|
||||||
*
|
*
|
||||||
|
@ -723,6 +1062,163 @@ export class AddonCalendarProvider {
|
||||||
return this.getEventsListPrefixCacheKey() + daysToStart + ':' + daysInterval;
|
return this.getEventsListPrefixCacheKey() + daysToStart + ':' + daysInterval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get monthly calendar events.
|
||||||
|
*
|
||||||
|
* @param {number} year Year to get.
|
||||||
|
* @param {number} month Month to get.
|
||||||
|
* @param {number} [courseId] Course to get.
|
||||||
|
* @param {number} [categoryId] Category to get.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<any>} Promise resolved with the response.
|
||||||
|
*/
|
||||||
|
getMonthlyEvents(year: number, month: number, courseId?: number, categoryId?: number, siteId?: string): Promise<any> {
|
||||||
|
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
|
||||||
|
const data: any = {
|
||||||
|
year: year,
|
||||||
|
month: month,
|
||||||
|
mini: 1 // Set mini to 1 to prevent returning the course selector HTML.
|
||||||
|
};
|
||||||
|
|
||||||
|
if (courseId) {
|
||||||
|
data.courseid = courseId;
|
||||||
|
}
|
||||||
|
if (categoryId) {
|
||||||
|
data.categoryid = categoryId;
|
||||||
|
}
|
||||||
|
|
||||||
|
const preSets = {
|
||||||
|
cacheKey: this.getMonthlyEventsCacheKey(year, month, courseId, categoryId),
|
||||||
|
updateFrequency: CoreSite.FREQUENCY_SOMETIMES
|
||||||
|
};
|
||||||
|
|
||||||
|
return site.read('core_calendar_get_calendar_monthly_view', data, preSets);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get prefix cache key for monthly events WS calls.
|
||||||
|
*
|
||||||
|
* @return {string} Prefix Cache key.
|
||||||
|
*/
|
||||||
|
protected getMonthlyEventsPrefixCacheKey(): string {
|
||||||
|
return this.ROOT_CACHE_KEY + 'monthly:';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get prefix cache key for a certain month for monthly events WS calls.
|
||||||
|
*
|
||||||
|
* @param {number} year Year to get.
|
||||||
|
* @param {number} month Month to get.
|
||||||
|
* @return {string} Prefix Cache key.
|
||||||
|
*/
|
||||||
|
protected getMonthlyEventsMonthPrefixCacheKey(year: number, month: number): string {
|
||||||
|
return this.getMonthlyEventsPrefixCacheKey() + year + ':' + month + ':';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get cache key for monthly events WS calls.
|
||||||
|
*
|
||||||
|
* @param {number} year Year to get.
|
||||||
|
* @param {number} month Month to get.
|
||||||
|
* @param {number} [courseId] Course to get.
|
||||||
|
* @param {number} [categoryId] Category to get.
|
||||||
|
* @return {string} Cache key.
|
||||||
|
*/
|
||||||
|
protected getMonthlyEventsCacheKey(year: number, month: number, courseId?: number, categoryId?: number): string {
|
||||||
|
return this.getMonthlyEventsMonthPrefixCacheKey(year, month) + (courseId ? courseId : '') + ':' +
|
||||||
|
(categoryId ? categoryId : '');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get upcoming calendar events.
|
||||||
|
*
|
||||||
|
* @param {number} [courseId] Course to get.
|
||||||
|
* @param {number} [categoryId] Category to get.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<any>} Promise resolved with the response.
|
||||||
|
*/
|
||||||
|
getUpcomingEvents(courseId?: number, categoryId?: number, siteId?: string): Promise<any> {
|
||||||
|
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
|
||||||
|
const data: any = {};
|
||||||
|
|
||||||
|
if (courseId) {
|
||||||
|
data.courseid = courseId;
|
||||||
|
}
|
||||||
|
if (categoryId) {
|
||||||
|
data.categoryid = categoryId;
|
||||||
|
}
|
||||||
|
|
||||||
|
const preSets = {
|
||||||
|
cacheKey: this.getUpcomingEventsCacheKey(courseId, categoryId),
|
||||||
|
updateFrequency: CoreSite.FREQUENCY_SOMETIMES
|
||||||
|
};
|
||||||
|
|
||||||
|
return site.read('core_calendar_get_calendar_upcoming_view', data, preSets);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get prefix cache key for upcoming events WS calls.
|
||||||
|
*
|
||||||
|
* @return {string} Prefix Cache key.
|
||||||
|
*/
|
||||||
|
protected getUpcomingEventsPrefixCacheKey(): string {
|
||||||
|
return this.ROOT_CACHE_KEY + 'upcoming:';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get cache key for upcoming events WS calls.
|
||||||
|
*
|
||||||
|
* @param {number} [courseId] Course to get.
|
||||||
|
* @param {number} [categoryId] Category to get.
|
||||||
|
* @return {string} Cache key.
|
||||||
|
*/
|
||||||
|
protected getUpcomingEventsCacheKey(courseId?: number, categoryId?: number): string {
|
||||||
|
return this.getUpcomingEventsPrefixCacheKey() + (courseId ? courseId : '') + ':' + (categoryId ? categoryId : '');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get URL to view a calendar.
|
||||||
|
*
|
||||||
|
* @param {string} view The view to load: 'month', 'day', 'upcoming', etc.
|
||||||
|
* @param {number} [time] Time to load. If not defined, current time.
|
||||||
|
* @param {string} [courseId] Course to load. If not defined, all courses.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<string>} Promise resolved with the URL.x
|
||||||
|
*/
|
||||||
|
getViewUrl(view: string, time?: number, courseId?: string, siteId?: string): Promise<string> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
let url = this.textUtils.concatenatePaths(site.getURL(), 'calendar/view.php?view=' + view);
|
||||||
|
|
||||||
|
if (time) {
|
||||||
|
url += '&time=' + time;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (courseId) {
|
||||||
|
url += '&course=' + courseId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return url;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the week days, already ordered according to a specified starting day.
|
||||||
|
*
|
||||||
|
* @param {number} [startingDay=0] Starting day. 0=Sunday, 1=Monday, ...
|
||||||
|
* @return {any[]} Week days.
|
||||||
|
*/
|
||||||
|
getWeekDays(startingDay?: number): any[] {
|
||||||
|
startingDay = startingDay || 0;
|
||||||
|
|
||||||
|
return this.weekDays.slice(startingDay).concat(this.weekDays.slice(0, startingDay));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invalidates access information.
|
* Invalidates access information.
|
||||||
*
|
*
|
||||||
|
@ -749,6 +1245,32 @@ export class AddonCalendarProvider {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalidates day events for all days.
|
||||||
|
*
|
||||||
|
* @param {string} [siteId] Site Id. If not defined, use current site.
|
||||||
|
* @return {Promise<any>} Promise resolved when the data is invalidated.
|
||||||
|
*/
|
||||||
|
invalidateAllDayEvents(siteId?: string): Promise<any> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
return site.invalidateWsCacheForKeyStartingWith(this.getDayEventsPrefixCacheKey());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalidates day events for a certain day.
|
||||||
|
*
|
||||||
|
* @param {number} year Year.
|
||||||
|
* @param {number} month Month.
|
||||||
|
* @param {number} day Day.
|
||||||
|
* @return {Promise<any>} Promise resolved when the data is invalidated.
|
||||||
|
*/
|
||||||
|
invalidateDayEvents(year: number, month: number, day: number, siteId?: string): Promise<any> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
return site.invalidateWsCacheForKeyStartingWith(this.getDayEventsDayPrefixCacheKey(year, month, day));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invalidates events list and all the single events and related info.
|
* Invalidates events list and all the single events and related info.
|
||||||
*
|
*
|
||||||
|
@ -782,6 +1304,77 @@ export class AddonCalendarProvider {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalidates monthly events for all months.
|
||||||
|
*
|
||||||
|
* @param {string} [siteId] Site Id. If not defined, use current site.
|
||||||
|
* @return {Promise<any>} Promise resolved when the data is invalidated.
|
||||||
|
*/
|
||||||
|
invalidateAllMonthlyEvents(siteId?: string): Promise<any> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
return site.invalidateWsCacheForKeyStartingWith(this.getMonthlyEventsPrefixCacheKey());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalidates monthly events for a certain months.
|
||||||
|
*
|
||||||
|
* @param {number} year Year.
|
||||||
|
* @param {number} month Month.
|
||||||
|
* @return {Promise<any>} Promise resolved when the data is invalidated.
|
||||||
|
*/
|
||||||
|
invalidateMonthlyEvents(year: number, month: number, siteId?: string): Promise<any> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
return site.invalidateWsCacheForKeyStartingWith(this.getMonthlyEventsMonthPrefixCacheKey(year, month));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalidates upcoming events for all courses and categories.
|
||||||
|
*
|
||||||
|
* @param {string} [siteId] Site Id. If not defined, use current site.
|
||||||
|
* @return {Promise<any>} Promise resolved when the data is invalidated.
|
||||||
|
*/
|
||||||
|
invalidateAllUpcomingEvents(siteId?: string): Promise<any> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
return site.invalidateWsCacheForKeyStartingWith(this.getUpcomingEventsPrefixCacheKey());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalidates upcoming events for a certain course or category.
|
||||||
|
*
|
||||||
|
* @param {number} [courseId] Course ID.
|
||||||
|
* @param {number} [categoryId] Category ID.
|
||||||
|
* @param {string} [siteId] Site Id. If not defined, use current site.
|
||||||
|
* @return {Promise<any>} Promise resolved when the data is invalidated.
|
||||||
|
*/
|
||||||
|
invalidateUpcomingEvents(courseId?: number, categoryId?: number, siteId?: string): Promise<any> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
return site.invalidateWsCacheForKeyStartingWith(this.getUpcomingEventsCacheKey(courseId, categoryId));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalidates look ahead setting.
|
||||||
|
*
|
||||||
|
* @param {string} [siteId] Site Id. If not defined, use current site.
|
||||||
|
* @return {Promise<any>} Promise resolved when the data is invalidated.
|
||||||
|
*/
|
||||||
|
invalidateLookAhead(siteId?: string): Promise<any> {
|
||||||
|
return this.userProvider.invalidateUserPreference('calendar_lookahead', siteId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalidates time format setting.
|
||||||
|
*
|
||||||
|
* @param {string} [siteId] Site Id. If not defined, use current site.
|
||||||
|
* @return {Promise<any>} Promise resolved when the data is invalidated.
|
||||||
|
*/
|
||||||
|
invalidateTimeFormat(siteId?: string): Promise<any> {
|
||||||
|
return this.userProvider.invalidateUserPreference('calendar_timeformat', siteId);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if Calendar is disabled in a certain site.
|
* Check if Calendar is disabled in a certain site.
|
||||||
*
|
*
|
||||||
|
|
|
@ -14,9 +14,11 @@
|
||||||
|
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { CoreLoggerProvider } from '@providers/logger';
|
import { CoreLoggerProvider } from '@providers/logger';
|
||||||
|
import { CoreSitesProvider } from '@providers/sites';
|
||||||
import { CoreCourseProvider } from '@core/course/providers/course';
|
import { CoreCourseProvider } from '@core/course/providers/course';
|
||||||
import { AddonCalendarProvider } from './calendar';
|
import { AddonCalendarProvider } from './calendar';
|
||||||
import { CoreConstants } from '@core/constants';
|
import { CoreConstants } from '@core/constants';
|
||||||
|
import * as moment from 'moment';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service that provides some features regarding lists of courses and categories.
|
* Service that provides some features regarding lists of courses and categories.
|
||||||
|
@ -33,11 +35,35 @@ export class AddonCalendarHelperProvider {
|
||||||
category: 'fa-cubes'
|
category: 'fa-cubes'
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(logger: CoreLoggerProvider, private courseProvider: CoreCourseProvider,
|
constructor(logger: CoreLoggerProvider,
|
||||||
|
private courseProvider: CoreCourseProvider,
|
||||||
|
private sitesProvider: CoreSitesProvider,
|
||||||
private calendarProvider: AddonCalendarProvider) {
|
private calendarProvider: AddonCalendarProvider) {
|
||||||
this.logger = logger.getInstance('AddonCalendarHelperProvider');
|
this.logger = logger.getInstance('AddonCalendarHelperProvider');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate some day data based on a list of events for that day.
|
||||||
|
*
|
||||||
|
* @param {any} day Day.
|
||||||
|
* @param {any[]} events Events.
|
||||||
|
*/
|
||||||
|
calculateDayData(day: any, events: any[]): void {
|
||||||
|
day.hasevents = events.length > 0;
|
||||||
|
day.haslastdayofevent = false;
|
||||||
|
|
||||||
|
const types = {};
|
||||||
|
events.forEach((event) => {
|
||||||
|
types[event.formattedType || event.eventtype] = true;
|
||||||
|
|
||||||
|
if (event.islastday) {
|
||||||
|
day.haslastdayofevent = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
day.calendareventtypes = Object.keys(types);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if current user can create/edit events.
|
* Check if current user can create/edit events.
|
||||||
*
|
*
|
||||||
|
@ -60,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.
|
* Convenience function to format some event data to be rendered.
|
||||||
*
|
*
|
||||||
|
@ -72,7 +133,9 @@ export class AddonCalendarHelperProvider {
|
||||||
e.moduleIcon = e.icon;
|
e.moduleIcon = e.icon;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e.id < 0) {
|
e.formattedType = this.calendarProvider.getEventType(e);
|
||||||
|
|
||||||
|
if (typeof e.duration != 'undefined') {
|
||||||
// It's an offline event, add some calculated data.
|
// It's an offline event, add some calculated data.
|
||||||
e.format = 1;
|
e.format = 1;
|
||||||
e.visible = 1;
|
e.visible = 1;
|
||||||
|
@ -115,6 +178,17 @@ export class AddonCalendarHelperProvider {
|
||||||
return options;
|
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.
|
* Check if the data of an event has changed.
|
||||||
*
|
*
|
||||||
|
@ -155,4 +229,51 @@ export class AddonCalendarHelperProvider {
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if an event should be displayed based on the filter.
|
||||||
|
*
|
||||||
|
* @param {any} event Event object.
|
||||||
|
* @param {number} courseId Course ID to filter.
|
||||||
|
* @param {number} categoryId Category ID the course belongs to.
|
||||||
|
* @param {any} categories Categories indexed by ID.
|
||||||
|
* @return {boolean} Whether it should be displayed.
|
||||||
|
*/
|
||||||
|
shouldDisplayEvent(event: any, courseId: number, categoryId: number, categories: any): boolean {
|
||||||
|
if (event.eventtype == 'user' || event.eventtype == 'site') {
|
||||||
|
// User or site event, display it.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.eventtype == 'category') {
|
||||||
|
if (!event.categoryid || !Object.keys(categories).length) {
|
||||||
|
// We can't tell if the course belongs to the category, display them all.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.categoryid == categoryId) {
|
||||||
|
// The event is in the same category as the course, display it.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check parent categories.
|
||||||
|
let category = categories[categoryId];
|
||||||
|
while (category) {
|
||||||
|
if (!category.parent) {
|
||||||
|
// Category doesn't have parent, stop.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.categoryid == category.parent) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
category = categories[category.parent];
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show the event if it is from site home or if it matches the selected course.
|
||||||
|
return event.courseid === this.sitesProvider.getSiteHomeId() || event.courseid == courseId;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,7 +44,7 @@ export class AddonCalendarMainMenuHandler implements CoreMainMenuHandler {
|
||||||
return {
|
return {
|
||||||
icon: 'calendar',
|
icon: 'calendar',
|
||||||
title: 'addon.calendar.calendar',
|
title: 'addon.calendar.calendar',
|
||||||
page: 'AddonCalendarListPage',
|
page: this.calendarProvider.canViewMonthInSite() ? 'AddonCalendarIndexPage' : 'AddonCalendarListPage',
|
||||||
class: 'addon-calendar-handler'
|
class: 'addon-calendar-handler'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,113 @@
|
||||||
|
// (C) Copyright 2015 Martin Dougiamas
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { CoreContentLinksHandlerBase } from '@core/contentlinks/classes/base-handler';
|
||||||
|
import { CoreContentLinksAction } from '@core/contentlinks/providers/delegate';
|
||||||
|
import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper';
|
||||||
|
import { AddonCalendarProvider } from './calendar';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Content links handler for calendar view page.
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class AddonCalendarViewLinkHandler extends CoreContentLinksHandlerBase {
|
||||||
|
name = 'AddonCalendarViewLinkHandler';
|
||||||
|
pattern = /\/calendar\/view\.php/;
|
||||||
|
|
||||||
|
protected SUPPORTED_VIEWS = ['month', 'mini', 'minithree', 'day', 'upcoming', 'upcoming_mini'];
|
||||||
|
|
||||||
|
constructor(private linkHelper: CoreContentLinksHelperProvider, private calendarProvider: AddonCalendarProvider) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the list of actions for a link (url).
|
||||||
|
*
|
||||||
|
* @param {string[]} siteIds List of sites the URL belongs to.
|
||||||
|
* @param {string} url The URL to treat.
|
||||||
|
* @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1}
|
||||||
|
* @param {number} [courseId] Course ID related to the URL. Optional but recommended.
|
||||||
|
* @return {CoreContentLinksAction[]|Promise<CoreContentLinksAction[]>} List of (or promise resolved with list of) actions.
|
||||||
|
*/
|
||||||
|
getActions(siteIds: string[], url: string, params: any, courseId?: number):
|
||||||
|
CoreContentLinksAction[] | Promise<CoreContentLinksAction[]> {
|
||||||
|
return [{
|
||||||
|
action: (siteId, navCtrl?): void => {
|
||||||
|
if (!params.view || params.view == 'month' || params.view == 'mini' || params.view == 'minithree') {
|
||||||
|
// Monthly view, open the calendar tab.
|
||||||
|
const stateParams: any = {
|
||||||
|
courseId: params.course
|
||||||
|
},
|
||||||
|
timestamp = params.time ? params.time * 1000 : Date.now();
|
||||||
|
|
||||||
|
const date = new Date(timestamp);
|
||||||
|
stateParams.year = date.getFullYear();
|
||||||
|
stateParams.month = date.getMonth() + 1;
|
||||||
|
|
||||||
|
this.linkHelper.goInSite(navCtrl, 'AddonCalendarIndexPage', stateParams, siteId); // @todo: Add checkMenu param.
|
||||||
|
|
||||||
|
} else if (params.view == 'day') {
|
||||||
|
// Daily view, open the page.
|
||||||
|
const stateParams: any = {
|
||||||
|
courseId: params.course
|
||||||
|
},
|
||||||
|
timestamp = params.time ? params.time * 1000 : Date.now();
|
||||||
|
|
||||||
|
const date = new Date(timestamp);
|
||||||
|
stateParams.year = date.getFullYear();
|
||||||
|
stateParams.month = date.getMonth() + 1;
|
||||||
|
stateParams.day = date.getDate();
|
||||||
|
|
||||||
|
this.linkHelper.goInSite(navCtrl, 'AddonCalendarDayPage', stateParams, siteId);
|
||||||
|
|
||||||
|
} else if (params.view == 'upcoming' || params.view == 'upcoming_mini') {
|
||||||
|
// Upcoming view, open the calendar tab.
|
||||||
|
const stateParams: any = {
|
||||||
|
courseId: params.course,
|
||||||
|
upcoming: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.linkHelper.goInSite(navCtrl, 'AddonCalendarIndexPage', stateParams, siteId); // @todo: Add checkMenu param.
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the handler is enabled for a certain site (site + user) and a URL.
|
||||||
|
* If not defined, defaults to true.
|
||||||
|
*
|
||||||
|
* @param {string} siteId The site ID.
|
||||||
|
* @param {string} url The URL to treat.
|
||||||
|
* @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1}
|
||||||
|
* @param {number} [courseId] Course ID related to the URL. Optional but recommended.
|
||||||
|
* @return {boolean|Promise<boolean>} Whether the handler is enabled for the URL and site.
|
||||||
|
*/
|
||||||
|
isEnabled(siteId: string, url: string, params: any, courseId?: number): boolean | Promise<boolean> {
|
||||||
|
if (params.view && this.SUPPORTED_VIEWS.indexOf(params.view) == -1) {
|
||||||
|
// This type of view isn't supported in the app.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.calendarProvider.isDisabled(siteId).then((disabled) => {
|
||||||
|
if (disabled) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.calendarProvider.canViewMonth(siteId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -1152,3 +1152,8 @@ ion-app.platform-desktop {
|
||||||
min-height: $button-ios-small-height;
|
min-height: $button-ios-small-height;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Make funnel icon have iOS look.
|
||||||
|
.ion-md-funnel::before {
|
||||||
|
content: "\f182";
|
||||||
|
}
|
||||||
|
|
|
@ -83,12 +83,15 @@
|
||||||
"addon.blog.publishtoworld": "Anyone in the world",
|
"addon.blog.publishtoworld": "Anyone in the world",
|
||||||
"addon.blog.showonlyyourentries": "Show only your entries",
|
"addon.blog.showonlyyourentries": "Show only your entries",
|
||||||
"addon.blog.siteblogheading": "Site blog",
|
"addon.blog.siteblogheading": "Site blog",
|
||||||
|
"addon.calendar.allday": "All day",
|
||||||
"addon.calendar.calendar": "Calendar",
|
"addon.calendar.calendar": "Calendar",
|
||||||
"addon.calendar.calendarevent": "Calendar event",
|
"addon.calendar.calendarevent": "Calendar event",
|
||||||
"addon.calendar.calendarevents": "Calendar events",
|
"addon.calendar.calendarevents": "Calendar events",
|
||||||
"addon.calendar.calendarreminders": "Calendar reminders",
|
"addon.calendar.calendarreminders": "Calendar reminders",
|
||||||
"addon.calendar.confirmeventdelete": "Are you sure you want to delete the \"{{$a}}\" event?",
|
"addon.calendar.confirmeventdelete": "Are you sure you want to delete the \"{{$a}}\" event?",
|
||||||
"addon.calendar.confirmeventseriesdelete": "The \"{{$a.name}}\" event is part of a series. Do you want to delete just this event, or all {{$a.count}} events in the series?",
|
"addon.calendar.confirmeventseriesdelete": "The \"{{$a.name}}\" event is part of a series. Do you want to delete just this event, or all {{$a.count}} events in the series?",
|
||||||
|
"addon.calendar.daynext": "Next day",
|
||||||
|
"addon.calendar.dayprev": "Previous day",
|
||||||
"addon.calendar.defaultnotificationtime": "Default notification time",
|
"addon.calendar.defaultnotificationtime": "Default notification time",
|
||||||
"addon.calendar.deleteallevents": "Delete all events",
|
"addon.calendar.deleteallevents": "Delete all events",
|
||||||
"addon.calendar.deleteevent": "Delete event",
|
"addon.calendar.deleteevent": "Delete event",
|
||||||
|
@ -106,9 +109,14 @@
|
||||||
"addon.calendar.eventname": "Event title",
|
"addon.calendar.eventname": "Event title",
|
||||||
"addon.calendar.eventstarttime": "Start time",
|
"addon.calendar.eventstarttime": "Start time",
|
||||||
"addon.calendar.eventtype": "Event type",
|
"addon.calendar.eventtype": "Event type",
|
||||||
|
"addon.calendar.fri": "Fri",
|
||||||
|
"addon.calendar.friday": "Friday",
|
||||||
"addon.calendar.gotoactivity": "Go to activity",
|
"addon.calendar.gotoactivity": "Go to activity",
|
||||||
"addon.calendar.invalidtimedurationminutes": "The duration in minutes you have entered is invalid. Please enter the duration in minutes greater than 0 or select no duration.",
|
"addon.calendar.invalidtimedurationminutes": "The duration in minutes you have entered is invalid. Please enter the duration in minutes greater than 0 or select no duration.",
|
||||||
"addon.calendar.invalidtimedurationuntil": "The date and time you selected for duration until is before the start time of the event. Please correct this before proceeding.",
|
"addon.calendar.invalidtimedurationuntil": "The date and time you selected for duration until is before the start time of the event. Please correct this before proceeding.",
|
||||||
|
"addon.calendar.mon": "Mon",
|
||||||
|
"addon.calendar.monday": "Monday",
|
||||||
|
"addon.calendar.monthlyview": "Monthly view",
|
||||||
"addon.calendar.newevent": "New event",
|
"addon.calendar.newevent": "New event",
|
||||||
"addon.calendar.noevents": "There are no events",
|
"addon.calendar.noevents": "There are no events",
|
||||||
"addon.calendar.nopermissiontoupdatecalendar": "Sorry, but you do not have permission to update the calendar event",
|
"addon.calendar.nopermissiontoupdatecalendar": "Sorry, but you do not have permission to update the calendar event",
|
||||||
|
@ -118,7 +126,17 @@
|
||||||
"addon.calendar.repeateditthis": "Apply changes to this event only",
|
"addon.calendar.repeateditthis": "Apply changes to this event only",
|
||||||
"addon.calendar.repeatevent": "Repeat this event",
|
"addon.calendar.repeatevent": "Repeat this event",
|
||||||
"addon.calendar.repeatweeksl": "Repeat weekly, creating altogether",
|
"addon.calendar.repeatweeksl": "Repeat weekly, creating altogether",
|
||||||
|
"addon.calendar.sat": "Sat",
|
||||||
|
"addon.calendar.saturday": "Saturday",
|
||||||
"addon.calendar.setnewreminder": "Set a new reminder",
|
"addon.calendar.setnewreminder": "Set a new reminder",
|
||||||
|
"addon.calendar.sun": "Sun",
|
||||||
|
"addon.calendar.sunday": "Sunday",
|
||||||
|
"addon.calendar.thu": "Thu",
|
||||||
|
"addon.calendar.thursday": "Thursday",
|
||||||
|
"addon.calendar.today": "Today",
|
||||||
|
"addon.calendar.tomorrow": "Tomorrow",
|
||||||
|
"addon.calendar.tue": "Tue",
|
||||||
|
"addon.calendar.tuesday": "Tuesday",
|
||||||
"addon.calendar.typecategory": "Category event",
|
"addon.calendar.typecategory": "Category event",
|
||||||
"addon.calendar.typeclose": "Close event",
|
"addon.calendar.typeclose": "Close event",
|
||||||
"addon.calendar.typecourse": "Course event",
|
"addon.calendar.typecourse": "Course event",
|
||||||
|
@ -128,6 +146,11 @@
|
||||||
"addon.calendar.typeopen": "Open event",
|
"addon.calendar.typeopen": "Open event",
|
||||||
"addon.calendar.typesite": "Site event",
|
"addon.calendar.typesite": "Site event",
|
||||||
"addon.calendar.typeuser": "User event",
|
"addon.calendar.typeuser": "User event",
|
||||||
|
"addon.calendar.upcomingevents": "Upcoming events",
|
||||||
|
"addon.calendar.wed": "Wed",
|
||||||
|
"addon.calendar.wednesday": "Wednesday",
|
||||||
|
"addon.calendar.when": "When",
|
||||||
|
"addon.calendar.yesterday": "Yesterday",
|
||||||
"addon.competency.activities": "Activities",
|
"addon.competency.activities": "Activities",
|
||||||
"addon.competency.competencies": "Competencies",
|
"addon.competency.competencies": "Competencies",
|
||||||
"addon.competency.competenciesmostoftennotproficientincourse": "Competencies most often not proficient in this course",
|
"addon.competency.competenciesmostoftennotproficientincourse": "Competencies most often not proficient in this course",
|
||||||
|
@ -1665,6 +1688,7 @@
|
||||||
"core.notingroup": "Sorry, but you need to be part of a group to see this page.",
|
"core.notingroup": "Sorry, but you need to be part of a group to see this page.",
|
||||||
"core.notsent": "Not sent",
|
"core.notsent": "Not sent",
|
||||||
"core.now": "now",
|
"core.now": "now",
|
||||||
|
"core.nummore": "{{$a}} more",
|
||||||
"core.numwords": "{{$a}} words",
|
"core.numwords": "{{$a}} words",
|
||||||
"core.offline": "Offline",
|
"core.offline": "Offline",
|
||||||
"core.ok": "OK",
|
"core.ok": "OK",
|
||||||
|
|
|
@ -13,9 +13,12 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
|
import { PopoverController } from 'ionic-angular';
|
||||||
import { CoreUtilsProvider } from '@providers/utils/utils';
|
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||||
import { CoreCoursesProvider } from './courses';
|
import { CoreCoursesProvider } from './courses';
|
||||||
import { AddonCourseCompletionProvider } from '@addon/coursecompletion/providers/coursecompletion';
|
import { AddonCourseCompletionProvider } from '@addon/coursecompletion/providers/coursecompletion';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { CoreCoursePickerMenuPopoverComponent } from '@components/course-picker-menu/course-picker-menu-popover';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper to gather some common courses functions.
|
* Helper to gather some common courses functions.
|
||||||
|
@ -23,8 +26,46 @@ import { AddonCourseCompletionProvider } from '@addon/coursecompletion/providers
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class CoreCoursesHelperProvider {
|
export class CoreCoursesHelperProvider {
|
||||||
|
|
||||||
constructor(private coursesProvider: CoreCoursesProvider, private utils: CoreUtilsProvider,
|
constructor(private coursesProvider: CoreCoursesProvider,
|
||||||
private courseCompletionProvider: AddonCourseCompletionProvider) { }
|
private utils: CoreUtilsProvider,
|
||||||
|
private courseCompletionProvider: AddonCourseCompletionProvider,
|
||||||
|
private translate: TranslateService,
|
||||||
|
private popoverCtrl: PopoverController) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the courses to display the course picker popover. If a courseId is specified, it will also return its categoryId.
|
||||||
|
*
|
||||||
|
* @param {number} [courseId] Course ID to get the category.
|
||||||
|
* @return {Promise<{courses: any[], categoryId: number}>} Promise resolved with the list of courses and the category.
|
||||||
|
*/
|
||||||
|
getCoursesForPopover(courseId?: number): Promise<{courses: any[], categoryId: number}> {
|
||||||
|
return this.coursesProvider.getUserCourses(false).then((courses) => {
|
||||||
|
// Add "All courses".
|
||||||
|
courses.unshift({
|
||||||
|
id: -1,
|
||||||
|
fullname: this.translate.instant('core.fulllistofcourses'),
|
||||||
|
category: -1
|
||||||
|
});
|
||||||
|
|
||||||
|
let categoryId;
|
||||||
|
|
||||||
|
if (courseId) {
|
||||||
|
// Search the course to get the category.
|
||||||
|
const course = courses.find((course) => {
|
||||||
|
return course.id == courseId;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (course) {
|
||||||
|
categoryId = course.category;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
courses: courses,
|
||||||
|
categoryId: categoryId
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a course object returned by core_enrol_get_users_courses and another one returned by core_course_get_courses_by_field,
|
* Given a course object returned by core_enrol_get_users_courses and another one returned by core_course_get_courses_by_field,
|
||||||
|
@ -174,4 +215,33 @@ export class CoreCoursesHelperProvider {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show a context menu to select a course, and return the courseId and categoryId of the selected course (-1 for all courses).
|
||||||
|
* Returns an empty object if popover closed without picking a course.
|
||||||
|
*
|
||||||
|
* @param {MouseEvent} event Click event.
|
||||||
|
* @param {any[]} courses List of courses, from CoreCoursesHelperProvider.getCoursesForPopover.
|
||||||
|
* @param {number} courseId The course to select at start.
|
||||||
|
* @return {Promise<{courseId?: number, categoryId?: number}>} Promise resolved with the course ID and category ID.
|
||||||
|
*/
|
||||||
|
selectCourse(event: MouseEvent, courses: any[], courseId: number): Promise<{courseId?: number, categoryId?: number}> {
|
||||||
|
return new Promise((resolve, reject): any => {
|
||||||
|
const popover = this.popoverCtrl.create(CoreCoursePickerMenuPopoverComponent, {
|
||||||
|
courses: courses,
|
||||||
|
courseId: courseId
|
||||||
|
});
|
||||||
|
|
||||||
|
popover.onDidDismiss((course) => {
|
||||||
|
if (course) {
|
||||||
|
resolve({courseId: course.id, categoryId: course.category});
|
||||||
|
} else {
|
||||||
|
resolve({});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
popover.present({
|
||||||
|
ev: event
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -181,6 +181,7 @@
|
||||||
"notingroup": "Sorry, but you need to be part of a group to see this page.",
|
"notingroup": "Sorry, but you need to be part of a group to see this page.",
|
||||||
"notsent": "Not sent",
|
"notsent": "Not sent",
|
||||||
"now": "now",
|
"now": "now",
|
||||||
|
"nummore": "{{$a}} more",
|
||||||
"numwords": "{{$a}} words",
|
"numwords": "{{$a}} words",
|
||||||
"offline": "Offline",
|
"offline": "Offline",
|
||||||
"ok": "OK",
|
"ok": "OK",
|
||||||
|
|
|
@ -44,6 +44,17 @@ export class CoreUrlUtilsProvider {
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a URL and a text, return an HTML link.
|
||||||
|
*
|
||||||
|
* @param {string} url URL.
|
||||||
|
* @param {string} text Text of the link.
|
||||||
|
* @return {string} Link.
|
||||||
|
*/
|
||||||
|
buildLink(url: string, text: string): string {
|
||||||
|
return '<a href="' + url + '">' + text + '</a>';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extracts the parameters from a URL and stores them in an object.
|
* Extracts the parameters from a URL and stores them in an object.
|
||||||
*
|
*
|
||||||
|
|
|
@ -29,13 +29,13 @@ $green: #5e8100; // Accent.
|
||||||
$red: #cb3d4d;
|
$red: #cb3d4d;
|
||||||
$orange: #f98012; // Accent (never text).
|
$orange: #f98012; // Accent (never text).
|
||||||
$yellow: #fbad1a; // Accent (never text).
|
$yellow: #fbad1a; // Accent (never text).
|
||||||
|
$purple: #8e24aa; // Accent (never text).
|
||||||
$core-color: $orange;
|
$core-color: $orange;
|
||||||
|
|
||||||
// Branded apps customization
|
// Branded apps customization
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
@import "bmma";
|
@import "bmma";
|
||||||
|
|
||||||
|
|
||||||
$blue-light: mix($blue, white, 20%) !default; // Background.
|
$blue-light: mix($blue, white, 20%) !default; // Background.
|
||||||
$blue-dark: darken($blue, 10%) !default;
|
$blue-dark: darken($blue, 10%) !default;
|
||||||
|
|
||||||
|
@ -76,19 +76,31 @@ $content-padding: 10px;
|
||||||
// colors so you can add, rename and remove colors as needed.
|
// colors so you can add, rename and remove colors as needed.
|
||||||
// The "primary" color is the only required color in the map.
|
// The "primary" color is the only required color in the map.
|
||||||
|
|
||||||
|
$primary: $core-color !default;
|
||||||
|
$secondary: $turquoise !default;
|
||||||
|
$danger: $red !default;
|
||||||
|
$light: $gray-lighter !default;
|
||||||
|
$color-gray: $gray-dark !default;
|
||||||
|
$dark: $black !default;
|
||||||
|
$warning: $yellow !default;
|
||||||
|
$success: $green !default;
|
||||||
|
$info: $blue !default;
|
||||||
|
$inverted-base: $white !default;
|
||||||
|
$inverted-contrast: $primary !default;
|
||||||
|
|
||||||
$colors: (
|
$colors: (
|
||||||
primary: $core-color,
|
primary: $primary,
|
||||||
secondary: $turquoise,
|
secondary: $secondary,
|
||||||
danger: $red,
|
danger: $danger,
|
||||||
light: $gray-lighter,
|
light: $light,
|
||||||
gray: $gray-dark,
|
gray: $color-gray,
|
||||||
dark: $black,
|
dark: $dark,
|
||||||
warning: $yellow,
|
warning: $warning,
|
||||||
success: $green,
|
success: $success,
|
||||||
info: $blue,
|
info: $info,
|
||||||
inverted: (
|
inverted: (
|
||||||
base: $white,
|
base: $inverted-base,
|
||||||
contrast: $core-color
|
contrast: $inverted-contrast
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue