MOBILE-3021 calendar: Support monthly view

main
Dani Palou 2019-07-03 08:13:02 +02:00
parent fa837532d4
commit 661709acb4
15 changed files with 678 additions and 54 deletions

View File

@ -106,9 +106,13 @@
"addon.calendar.eventname": "calendar",
"addon.calendar.eventstarttime": "calendar",
"addon.calendar.eventtype": "calendar",
"addon.calendar.fri": "calendar",
"addon.calendar.friday": "calendar",
"addon.calendar.gotoactivity": "calendar",
"addon.calendar.invalidtimedurationminutes": "calendar",
"addon.calendar.invalidtimedurationuntil": "calendar",
"addon.calendar.mon": "calendar",
"addon.calendar.monday": "calendar",
"addon.calendar.newevent": "calendar",
"addon.calendar.noevents": "local_moodlemobileapp",
"addon.calendar.nopermissiontoupdatecalendar": "error",
@ -118,7 +122,15 @@
"addon.calendar.repeateditthis": "calendar",
"addon.calendar.repeatevent": "calendar",
"addon.calendar.repeatweeksl": "calendar",
"addon.calendar.sat": "calendar",
"addon.calendar.saturday": "calendar",
"addon.calendar.setnewreminder": "local_moodlemobileapp",
"addon.calendar.sun": "calendar",
"addon.calendar.sunday": "calendar",
"addon.calendar.thu": "calendar",
"addon.calendar.thursday": "calendar",
"addon.calendar.tue": "calendar",
"addon.calendar.tuesday": "calendar",
"addon.calendar.typecategory": "calendar",
"addon.calendar.typeclose": "calendar",
"addon.calendar.typecourse": "calendar",
@ -128,6 +140,8 @@
"addon.calendar.typeopen": "calendar",
"addon.calendar.typesite": "calendar",
"addon.calendar.typeuser": "calendar",
"addon.calendar.wed": "calendar",
"addon.calendar.wednesday": "calendar",
"addon.competency.activities": "tool_lp",
"addon.competency.competencies": "competency",
"addon.competency.competenciesmostoftennotproficientincourse": "tool_lp",
@ -1657,6 +1671,7 @@
"core.notingroup": "moodle",
"core.notsent": "local_moodlemobileapp",
"core.now": "moodle",
"core.nummore": "local_moodlemobileapp",
"core.numwords": "moodle",
"core.offline": "message",
"core.ok": "moodle",

View File

@ -0,0 +1,52 @@
<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>
<p>{{ periodName }}</p>
</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 no-padding>
<!-- List of days. -->
<ion-row>
<ion-col text-center *ngFor="let day of weekDays" class="addon-calendar-weekdays">
<p>{{ day.shortname | translate }}</p>
</ion-col>
</ion-row>
<!-- Weeks. -->
<ion-row *ngFor="let week of weeks">
<ion-col *ngFor="let value of week.prepadding" class="dayblank"></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}' >
<p>{{ day.mday }}</p>
<!-- In phone, display some dots to indicate the type of events. -->
<p class="hidden-tablet"><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" class="addon-calendar-day-events">
<p *ngFor="let event of day.filteredEvents | slice:0:3">
<span class="calendar_event_type calendar_event_{{event.eventtype}}"></span>
{{event.name}}
</p>
<p *ngIf="day.filteredEvents.length > 3">{{ 'core.nummore' | translate:{$a: day.filteredEvents.length - 3} }}</p>
</div>
</ion-col>
<ion-col *ngFor="let value of week.postpadding" class="dayblank"></ion-col> <!-- Empty slots (last week). -->
</ion-row>
</ion-grid>
</core-loading>

View File

@ -0,0 +1,42 @@
$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.
ion-app.app-root addon-calendar-calendar {
.addon-calendar-weekdays {
opacity: 0.4;
}
.addon-calendar-day-events {
@include text-align('start');
}
.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;
}
}
}

View File

@ -0,0 +1,221 @@
// (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 } from '@angular/core';
import { CoreEventsProvider } from '@providers/events';
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 { 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.
periodName: string;
weekDays: any[];
weeks: any[];
loaded = false;
protected year: number;
protected month: number;
protected categoriesRetrieved = false;
protected categories = {};
constructor(eventsProvider: CoreEventsProvider,
sitesProvider: CoreSitesProvider,
private calendarProvider: AddonCalendarProvider,
private calendarHelper: AddonCalendarHelperProvider,
private domUtils: CoreDomUtilsProvider,
private timeUtils: CoreTimeUtilsProvider,
private utils: CoreUtilsProvider,
private coursesProvider: CoreCoursesProvider) {
}
/**
* Component loaded.
*/
ngOnInit(): void {
const now = new Date();
this.year = this.initialYear ? Number(this.initialYear) : now.getFullYear();
this.month = this.initialYear ? Number(this.initialYear) : now.getMonth() + 1;
this.canNavigate = typeof this.canNavigate == 'undefined' ? true : this.utils.isTrueOrOne(this.canNavigate);
this.fetchData();
}
/**
* Detect changes on input properties.
*/
ngOnChanges(changes: {[name: string]: SimpleChange}): void {
if ((changes.courseId || changes.categoryId) && this.weeks) {
const courseId = this.courseId ? Number(this.courseId) : undefined,
categoryId = this.categoryId ? Number(this.categoryId) : undefined;
this.filterEvents(courseId, categoryId);
}
}
/**
* Fetch contacts.
*
* @param {boolean} [refresh=false] True if we are refreshing contacts, false if we are loading more.
* @return {Promise<any>} Promise resolved when done.
*/
fetchData(refresh: boolean = false): Promise<any> {
const courseId = this.courseId ? Number(this.courseId) : undefined,
categoryId = this.categoryId ? Number(this.categoryId) : undefined,
promises = [];
promises.push(this.loadCategories());
promises.push(this.calendarProvider.getMonthlyEvents(this.year, this.month, courseId, categoryId).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;
this.filterEvents(courseId, categoryId);
}));
return Promise.all(promises).catch((error) => {
this.domUtils.showErrorModalDefault(error, 'addon.calendar.errorloadevents', true);
}).finally(() => {
this.loaded = true;
});
}
/**
* 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.
*
* @param {number} courseId Course ID.
* @param {number} categoryId Category the course belongs to.
*/
filterEvents(courseId: number, categoryId: number): void {
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.
*
* @return {Promise<any>} Promise resolved when done.
*/
refreshData(): Promise<any> {
const promises = [];
promises.push(this.calendarProvider.invalidateMonthlyEvents(this.year, this.month));
promises.push(this.coursesProvider.invalidateCategories(0, true));
this.categoriesRetrieved = false; // Get categories again.
return Promise.all(promises).then(() => {
return this.fetchData(true);
});
}
/**
* Load next month.
*/
loadNext(): void {
if (this.month === 12) {
this.month = 1;
this.year++;
} else {
this.month++;
}
this.loaded = false;
this.fetchData();
}
/**
* Load previous month.
*/
loadPrevious(): void {
if (this.month === 1) {
this.month = 12;
this.year--;
} else {
this.month--;
}
this.loaded = false;
this.fetchData();
}
/**
* Component destroyed.
*/
ngOnDestroy(): void {
// @todo
}
}

View File

@ -0,0 +1,40 @@
// (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 { AddonCalendarCalendarComponent } from '../components/calendar/calendar';
@NgModule({
declarations: [
AddonCalendarCalendarComponent
],
imports: [
CommonModule,
IonicModule,
TranslateModule.forChild(),
CoreComponentsModule,
CoreDirectivesModule
],
providers: [
],
exports: [
AddonCalendarCalendarComponent
]
})
export class AddonCalendarComponentsModule {}

View File

@ -22,9 +22,13 @@
"eventname": "Event title",
"eventstarttime": "Start time",
"eventtype": "Event type",
"fri": "Fri",
"friday": "Friday",
"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.",
"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",
"newevent": "New event",
"noevents": "There are no events",
"nopermissiontoupdatecalendar": "Sorry, but you do not have permission to update the calendar event",
@ -34,7 +38,15 @@
"repeateditthis": "Apply changes to this event only",
"repeatevent": "Repeat this event",
"repeatweeksl": "Repeat weekly, creating altogether",
"sat": "Sat",
"saturday": "Saturday",
"setnewreminder": "Set a new reminder",
"sun": "Sun",
"sunday": "Sunday",
"thu": "Thu",
"thursday": "Thursday",
"tue": "Tue",
"tuesday": "Tuesday",
"typeclose": "Close event",
"typecourse": "Course event",
"typecategory": "Category event",
@ -43,5 +55,7 @@
"typegroup": "Group event",
"typeopen": "Open event",
"typesite": "Site event",
"typeuser": "User event"
"typeuser": "User event",
"wed": "Wed",
"wednesday": "Wednesday"
}

View File

@ -15,9 +15,8 @@
<ion-refresher [enabled]="loaded" (ionRefresh)="doRefresh($event)">
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
</ion-refresher>
<core-loading [hideUntil]="loaded">
</core-loading>
<addon-calendar-calendar [courseId]="courseId" [categoryId]="categoryId"></addon-calendar-calendar>
<!-- Create a calendar event. -->
<ion-fab core-fab bottom end *ngIf="canCreate">

View File

@ -18,6 +18,7 @@ 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({
@ -28,6 +29,7 @@ import { AddonCalendarIndexPage } from './index';
CoreComponentsModule,
CoreDirectivesModule,
CorePipesModule,
AddonCalendarComponentsModule,
IonicPageModule.forChild(AddonCalendarIndexPage),
TranslateModule.forChild()
],

View File

@ -8,12 +8,20 @@
//
// 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.
// 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 } from '@angular/core';
import { IonicPage } from 'ionic-angular';
import { Component, OnInit, ViewChild } from '@angular/core';
import { IonicPage, NavParams, NavController, PopoverController } from 'ionic-angular';
import { CoreLocalNotificationsProvider } from '@providers/local-notifications';
import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { AddonCalendarProvider } from '../../providers/calendar';
import { AddonCalendarHelperProvider } from '../../providers/helper';
import { AddonCalendarCalendarComponent } from '../../components/calendar/calendar';
import { CoreCoursesProvider } from '@core/courses/providers/courses';
import { CoreCoursePickerMenuPopoverComponent } from '@components/course-picker-menu/course-picker-menu-popover';
import { TranslateService } from '@ngx-translate/core';
/**
* Page that displays the calendar events.
@ -24,15 +32,154 @@ import { IonicPage } from 'ionic-angular';
templateUrl: 'index.html',
})
export class AddonCalendarIndexPage implements OnInit {
@ViewChild(AddonCalendarCalendarComponent) calendarComponent: AddonCalendarCalendarComponent;
constructor() {
// @todo
protected allCourses = {
id: -1,
fullname: this.translate.instant('core.fulllistofcourses'),
category: -1
};
courseId: number;
categoryId: number;
canCreate = false;
courses: any[];
notificationsEnabled = false;
loaded = false;
constructor(localNotificationsProvider: CoreLocalNotificationsProvider,
navParams: NavParams,
private navCtrl: NavController,
private domUtils: CoreDomUtilsProvider,
private calendarProvider: AddonCalendarProvider,
private calendarHelper: AddonCalendarHelperProvider,
private translate: TranslateService,
private coursesProvider: CoreCoursesProvider,
private popoverCtrl: PopoverController) {
this.courseId = navParams.get('courseId');
this.notificationsEnabled = localNotificationsProvider.isAvailable();
}
/**
* View loaded.
*/
ngOnInit(): void {
// @todo
this.fetchData();
}
/**
* Fetch all the data required for the view.
*
* @return {Promise<any>} Promise resolved when done.
*/
fetchData(): Promise<any> {
const promises = [];
// Load courses for the popover.
promises.push(this.coursesProvider.getUserCourses(false).then((courses) => {
// Add "All courses".
courses.unshift(this.allCourses);
this.courses = courses;
if (this.courseId) {
// Search the course to get the category.
const course = this.courses.find((course) => {
return course.id == this.courseId;
});
if (course) {
this.categoryId = course.category;
}
}
}));
// Check if user can create events.
promises.push(this.calendarHelper.canEditEvents(this.courseId).then((canEdit) => {
this.canCreate = canEdit;
}));
return Promise.all(promises).catch((error) => {
this.domUtils.showErrorModalDefault(error, 'addon.calendar.errorloadevents', true);
}).finally(() => {
this.loaded = true;
});
}
/**
* Refresh the data.
*
* @param {any} [refresher] Refresher.
* @return {Promise<any>} Promise resolved when done.
*/
doRefresh(refresher?: any): void {
if (!this.loaded) {
return;
}
const promises = [];
promises.push(this.calendarProvider.invalidateAllowedEventTypes().then(() => {
return this.fetchData();
}));
// Refresh the sub-component.
promises.push(this.calendarComponent.refreshData());
Promise.all(promises).finally(() => {
refresher && refresher.complete();
});
}
/**
* Show the context menu.
*
* @param {MouseEvent} event Event.
*/
openCourseFilter(event: MouseEvent): void {
const popover = this.popoverCtrl.create(CoreCoursePickerMenuPopoverComponent, {
courses: this.courses,
courseId: this.courseId
});
popover.onDidDismiss((course) => {
if (course) {
this.courseId = course.id > 0 ? course.id : undefined;
this.categoryId = course.id > 0 ? course.category : 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;
});
}
});
popover.present({
ev: event
});
}
/**
* 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');
}
}

View File

@ -423,50 +423,10 @@ export class AddonCalendarListPage implements OnDestroy {
return this.events;
}
return this.events.filter(this.shouldDisplayEvent.bind(this));
}
/**
* 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;
return this.events.filter((event) => {
return this.calendarHelper.shouldDisplayEvent(event, this.filter.course.id, this.filter.course.category,
this.categories);
});
}
/**

View File

@ -51,6 +51,37 @@ export class AddonCalendarProvider {
static TYPE_USER = 'user';
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.
static EVENTS_TABLE = 'addon_calendar_events_2';
static REMINDERS_TABLE = 'addon_calendar_reminders';
@ -875,6 +906,18 @@ export class AddonCalendarProvider {
return this.getUpcomingEventsPrefixCacheKey() + (courseId ? courseId : '') + ':' + (categoryId ? categoryId : '');
}
/**
* 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.
*

View File

@ -14,6 +14,7 @@
import { Injectable } from '@angular/core';
import { CoreLoggerProvider } from '@providers/logger';
import { CoreSitesProvider } from '@providers/sites';
import { CoreCourseProvider } from '@core/course/providers/course';
import { AddonCalendarProvider } from './calendar';
import { CoreConstants } from '@core/constants';
@ -33,11 +34,35 @@ export class AddonCalendarHelperProvider {
category: 'fa-cubes'
};
constructor(logger: CoreLoggerProvider, private courseProvider: CoreCourseProvider,
constructor(logger: CoreLoggerProvider,
private courseProvider: CoreCourseProvider,
private sitesProvider: CoreSitesProvider,
private calendarProvider: AddonCalendarProvider) {
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.eventtype] = true;
if (event.islastday) {
day.haslastdayofevent = true;
}
});
day.calendareventtypes = Object.keys(types);
}
/**
* Check if current user can create/edit events.
*
@ -155,4 +180,51 @@ export class AddonCalendarHelperProvider {
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;
}
}

View File

@ -106,9 +106,13 @@
"addon.calendar.eventname": "Event title",
"addon.calendar.eventstarttime": "Start time",
"addon.calendar.eventtype": "Event type",
"addon.calendar.fri": "Fri",
"addon.calendar.friday": "Friday",
"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.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.newevent": "New event",
"addon.calendar.noevents": "There are no events",
"addon.calendar.nopermissiontoupdatecalendar": "Sorry, but you do not have permission to update the calendar event",
@ -118,7 +122,15 @@
"addon.calendar.repeateditthis": "Apply changes to this event only",
"addon.calendar.repeatevent": "Repeat this event",
"addon.calendar.repeatweeksl": "Repeat weekly, creating altogether",
"addon.calendar.sat": "Sat",
"addon.calendar.saturday": "Saturday",
"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.tue": "Tue",
"addon.calendar.tuesday": "Tuesday",
"addon.calendar.typecategory": "Category event",
"addon.calendar.typeclose": "Close event",
"addon.calendar.typecourse": "Course event",
@ -128,6 +140,8 @@
"addon.calendar.typeopen": "Open event",
"addon.calendar.typesite": "Site event",
"addon.calendar.typeuser": "User event",
"addon.calendar.wed": "Wed",
"addon.calendar.wednesday": "Wednesday",
"addon.competency.activities": "Activities",
"addon.competency.competencies": "Competencies",
"addon.competency.competenciesmostoftennotproficientincourse": "Competencies most often not proficient in this course",
@ -1658,6 +1672,7 @@
"core.notingroup": "Sorry, but you need to be part of a group to see this page.",
"core.notsent": "Not sent",
"core.now": "now",
"core.nummore": "{{$a}} more",
"core.numwords": "{{$a}} words",
"core.offline": "Offline",
"core.ok": "OK",

View File

@ -184,6 +184,7 @@
"notingroup": "Sorry, but you need to be part of a group to see this page.",
"notsent": "Not sent",
"now": "now",
"nummore": "{{$a}} more",
"numwords": "{{$a}} words",
"offline": "Offline",
"ok": "OK",

View File

@ -28,6 +28,7 @@ $green: #5e8100; // Accent.
$red: #cb3d4d;
$orange: #f98012; // Accent (never text).
$yellow: #fbad1a; // Accent (never text).
$purple: #8e24aa; // Accent (never text).
$core-color: $orange;
// Branded apps customization