MOBILE-1927 calendar: Allow creating events in online
parent
f7ea003b41
commit
cfaaefc184
|
@ -3,13 +3,26 @@
|
|||
"calendarevents": "Calendar events",
|
||||
"calendarreminders": "Calendar reminders",
|
||||
"defaultnotificationtime": "Default notification time",
|
||||
"durationminutes": "Duration in minutes",
|
||||
"durationnone": "Without duration",
|
||||
"durationuntil": "Until",
|
||||
"editevent": "Editing event",
|
||||
"errorloadevent": "Error loading event.",
|
||||
"errorloadevents": "Error loading events.",
|
||||
"eventduration": "Duration",
|
||||
"eventendtime": "End time",
|
||||
"eventname": "Event title",
|
||||
"eventstarttime": "Start time",
|
||||
"eventtype": "Event type",
|
||||
"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.",
|
||||
"newevent": "New event",
|
||||
"noevents": "There are no events",
|
||||
"nopermissiontoupdatecalendar": "Sorry, but you do not have permission to update the calendar event",
|
||||
"reminders": "Reminders",
|
||||
"repeatevent": "Repeat this event",
|
||||
"repeatweeksl": "Repeat weekly, creating altogether",
|
||||
"setnewreminder": "Set a new reminder",
|
||||
"typeclose": "Close event",
|
||||
"typecourse": "Course event",
|
||||
|
|
|
@ -0,0 +1,144 @@
|
|||
<ion-header>
|
||||
<ion-navbar core-back-button>
|
||||
<ion-title><core-format-text [text]="title | translate"></core-format-text></ion-title>
|
||||
</ion-navbar>
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
<ion-refresher [enabled]="loaded" (ionRefresh)="refreshData($event)">
|
||||
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||
</ion-refresher>
|
||||
|
||||
<core-loading [hideUntil]="loaded">
|
||||
<form ion-list [formGroup]="eventForm">
|
||||
<!-- Event name. -->
|
||||
<ion-item text-wrap>
|
||||
<ion-label stacked><h2 [core-mark-required]="true">{{ 'addon.calendar.eventname' | translate }}</h2></ion-label>
|
||||
<ion-input type="text" name="name" [placeholder]="'addon.calendar.eventname' | translate" [formControlName]="'name'"></ion-input>
|
||||
<core-input-errors item-content [control]="eventForm.controls.name" [errorMessages]="errors"></core-input-errors>
|
||||
</ion-item>
|
||||
|
||||
<!-- Date. -->
|
||||
<ion-item text-wrap>
|
||||
<ion-label stacked><h2 [core-mark-required]="true">{{ 'core.date' | translate }}</h2></ion-label>
|
||||
<ion-datetime [formControlName]="'timestart'" [placeholder]="'core.date' | translate" [displayFormat]="dateFormat"></ion-datetime>
|
||||
<core-input-errors item-content [control]="eventForm.controls.timestart" [errorMessages]="errors"></core-input-errors>
|
||||
</ion-item>
|
||||
|
||||
<!-- Type. -->
|
||||
<ion-item text-wrap class="addon-calendar-eventtype-container">
|
||||
<ion-label id="addon-calendar-eventtype-label"><h2 [core-mark-required]="true">{{ 'addon.calendar.eventtype' | translate }}</h2></ion-label>
|
||||
<ion-select [formControlName]="'eventtype'" aria-labelledby="addon-calendar-eventtype-label" interface="action-sheet" [disabled]="eventTypes.length == 1">
|
||||
<ion-option *ngFor="let type of eventTypes" [value]="type.value">{{ type.name | translate }}</ion-option>
|
||||
</ion-select>
|
||||
</ion-item>
|
||||
|
||||
<!-- Category. -->
|
||||
<ion-item text-wrap *ngIf="eventTypeControl.value == 'category'">
|
||||
<ion-label id="addon-calendar-category-label"><h2 [core-mark-required]="true">{{ 'core.category' | translate }}</h2></ion-label>
|
||||
<ion-select [formControlName]="'categoryid'" aria-labelledby="addon-calendar-category-label" interface="action-sheet" [placeholder]="'core.noselection' | translate">
|
||||
<ion-option *ngFor="let category of categories" [value]="category.id">{{ category.name }}</ion-option>
|
||||
</ion-select>
|
||||
</ion-item>
|
||||
|
||||
<!-- Course. -->
|
||||
<ion-item text-wrap *ngIf="eventTypeControl.value == 'course'">
|
||||
<ion-label id="addon-calendar-course-label"><h2 [core-mark-required]="true">{{ 'core.course' | translate }}</h2></ion-label>
|
||||
<ion-select [formControlName]="'courseid'" aria-labelledby="addon-calendar-course-label" interface="action-sheet" [placeholder]="'core.noselection' | translate">
|
||||
<ion-option *ngFor="let course of courses" [value]="course.id">{{ course.fullname }}</ion-option>
|
||||
</ion-select>
|
||||
</ion-item>
|
||||
|
||||
<!-- Group. -->
|
||||
<ng-container *ngIf="eventTypeControl.value == 'group'">
|
||||
<!-- Select the course. -->
|
||||
<ion-item text-wrap>
|
||||
<ion-label id="addon-calendar-groupcourse-label"><h2 [core-mark-required]="true">{{ 'core.course' | translate }}</h2></ion-label>
|
||||
<ion-select [formControlName]="'groupcourseid'" aria-labelledby="addon-calendar-groupcourse-label" interface="action-sheet" [placeholder]="'core.noselection' | translate" (ionChange)="groupCourseSelected($event)">
|
||||
<ion-option *ngFor="let course of courses" [value]="course.id">{{ course.fullname }}</ion-option>
|
||||
</ion-select>
|
||||
</ion-item>
|
||||
<!-- The course has no groups. -->
|
||||
<ion-item text-wrap *ngIf="!loadingGroups && courseGroupSet && !groups.length" class="core-danger-item">
|
||||
<p>{{ 'core.coursenogroups' | translate }}</p>
|
||||
</ion-item>
|
||||
<!-- Select the group. -->
|
||||
<ion-item text-wrap *ngIf="!loadingGroups && groups.length > 0">
|
||||
<ion-label id="addon-calendar-group-label"><h2 [core-mark-required]="true">{{ 'core.group' | translate }}</h2></ion-label>
|
||||
<ion-select [formControlName]="'groupid'" aria-labelledby="addon-calendar-group-label" interface="action-sheet" [placeholder]="'core.noselection' | translate">
|
||||
<ion-option *ngFor="let group of groups" [value]="group.id">{{ group.name }}</ion-option>
|
||||
</ion-select>
|
||||
</ion-item>
|
||||
<!-- Loading groups. -->
|
||||
<ion-item text-wrap *ngIf="loadingGroups">
|
||||
<ion-spinner *ngIf="loadingGroups"></ion-spinner>
|
||||
</ion-item>
|
||||
</ng-container>
|
||||
|
||||
<!-- Advanced options. -->
|
||||
<ion-item-divider text-wrap (click)="toggleAdvanced()" class="core-expandable">
|
||||
<core-icon *ngIf="!advanced" name="fa-caret-right" item-start></core-icon>
|
||||
<span *ngIf="!advanced">{{ 'core.showmore' | translate }}</span>
|
||||
<core-icon *ngIf="advanced" name="fa-caret-down" item-start></core-icon>
|
||||
<span *ngIf="advanced">{{ 'core.showless' | translate }}</span>
|
||||
</ion-item-divider>
|
||||
|
||||
<ng-container *ngIf="advanced">
|
||||
<!-- Description. -->
|
||||
<ion-item text-wrap>
|
||||
<ion-label stacked><h2>{{ 'core.description' | translate }}</h2></ion-label>
|
||||
<core-rich-text-editor item-content [control]="descriptionControl" [placeholder]="'core.description' | translate" name="description" [component]="component" [componentId]="eventId"></core-rich-text-editor>
|
||||
</ion-item>
|
||||
|
||||
<!-- Location. -->
|
||||
<ion-item text-wrap>
|
||||
<ion-label stacked><h2>{{ 'core.location' | translate }}</h2></ion-label>
|
||||
<ion-input type="text" name="location" [placeholder]="'core.location' | translate" [formControlName]="'location'"></ion-input>
|
||||
</ion-item>
|
||||
|
||||
<!-- Duration. -->
|
||||
<div text-wrap radio-group [formControlName]="'duration'" class="addon-calendar-duration-container">
|
||||
<ion-item class="addon-calendar-duration-title"><h2>{{ 'addon.calendar.eventduration' | translate }}</h2></ion-item>
|
||||
<ion-item>
|
||||
<ion-label>{{ 'addon.calendar.durationnone' | translate }}</ion-label>
|
||||
<ion-radio [value]="0"></ion-radio>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>{{ 'addon.calendar.durationuntil' | translate }}</ion-label>
|
||||
<ion-radio [value]="1"></ion-radio>
|
||||
</ion-item>
|
||||
<ion-item text-wrap>
|
||||
<ion-datetime [formControlName]="'timedurationuntil'" [placeholder]="'addon.calendar.durationuntil' | translate" [displayFormat]="dateFormat" [disabled]="eventForm.controls.duration.value != 1"></ion-datetime>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>{{ 'addon.calendar.durationminutes' | translate }}</ion-label>
|
||||
<ion-radio [value]="2"></ion-radio>
|
||||
</ion-item>
|
||||
<ion-item text-wrap>
|
||||
<ion-input type="number" name="timedurationminutes" [placeholder]="'addon.calendar.durationminutes' | translate" [formControlName]="'timedurationminutes'" [disabled]="eventForm.controls.duration.value != 2"></ion-input>
|
||||
</ion-item>
|
||||
</div>
|
||||
|
||||
<!-- Repeat. -->
|
||||
<ion-item text-wrap>
|
||||
<ion-label><h2>{{ 'addon.calendar.repeatevent' | translate }}</h2></ion-label>
|
||||
<ion-checkbox item-end [formControlName]="'repeat'"></ion-checkbox>
|
||||
</ion-item>
|
||||
<ion-item text-wrap *ngIf="eventForm.controls.repeat.value">
|
||||
<ion-label stacked><h2>{{ 'addon.calendar.repeatweeksl' | translate }}</h2></ion-label>
|
||||
<ion-input type="number" name="repeats" [formControlName]="'repeats'"></ion-input>
|
||||
</ion-item>
|
||||
</ng-container>
|
||||
|
||||
<ion-item>
|
||||
<ion-row>
|
||||
<ion-col>
|
||||
<button ion-button block (click)="submit()" [disabled]="!eventForm.valid">{{ 'core.save' | translate }}</button>
|
||||
</ion-col>
|
||||
<ion-col *ngIf="hasOffline">
|
||||
<button ion-button block color="light" (click)="discard()">{{ 'core.discard' | translate }}</button>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
</ion-item>
|
||||
</form>
|
||||
</core-loading>
|
||||
</ion-content>
|
|
@ -0,0 +1,33 @@
|
|||
// (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 { AddonCalendarEditEventPage } from './edit-event';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AddonCalendarEditEventPage,
|
||||
],
|
||||
imports: [
|
||||
CoreComponentsModule,
|
||||
CoreDirectivesModule,
|
||||
IonicPageModule.forChild(AddonCalendarEditEventPage),
|
||||
TranslateModule.forChild()
|
||||
],
|
||||
})
|
||||
export class AddonCalendarEditEventPageModule {}
|
|
@ -0,0 +1,35 @@
|
|||
ion-app.app-root page-addon-calendar-edit-event {
|
||||
.addon-calendar-duration-container ion-item:not(.addon-calendar-duration-title) {
|
||||
&.item-ios {
|
||||
@include padding-horizontal($item-ios-padding-start * 2, null);
|
||||
|
||||
ion-input {
|
||||
@include padding-horizontal($datetime-ios-padding-start - $text-input-ios-margin-start, null);
|
||||
}
|
||||
}
|
||||
&.item-md {
|
||||
@include padding-horizontal($item-md-padding-start * 2, null);
|
||||
|
||||
ion-input {
|
||||
@include padding-horizontal($datetime-md-padding-start - $text-input-md-margin-start, null);
|
||||
}
|
||||
}
|
||||
&.item-wp {
|
||||
@include padding-horizontal($item-wp-padding-start * 2, null);
|
||||
|
||||
ion-input {
|
||||
@include padding-horizontal($datetime-wp-padding-start - $text-input-wp-margin-start, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.addon-calendar-eventtype-container.item-select-disabled {
|
||||
ion-label, ion-select {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.select-icon {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,392 @@
|
|||
// (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, OnInit, Optional, ViewChild } from '@angular/core';
|
||||
import { FormControl, FormGroup, FormBuilder, Validators } from '@angular/forms';
|
||||
import { IonicPage, NavController, NavParams } from 'ionic-angular';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { CoreEventsProvider } from '@providers/events';
|
||||
import { CoreGroupsProvider } from '@providers/groups';
|
||||
import { CoreSitesProvider } from '@providers/sites';
|
||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||
import { CoreTimeUtilsProvider } from '@providers/utils/time';
|
||||
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||
import { CoreCoursesProvider } from '@core/courses/providers/courses';
|
||||
import { CoreSplitViewComponent } from '@components/split-view/split-view';
|
||||
import { CoreRichTextEditorComponent } from '@components/rich-text-editor/rich-text-editor.ts';
|
||||
import { AddonCalendarProvider } from '../../providers/calendar';
|
||||
import { AddonCalendarHelperProvider } from '../../providers/helper';
|
||||
import { CoreSite } from '@classes/site';
|
||||
|
||||
/**
|
||||
* Page that displays a form to create/edit an event.
|
||||
*/
|
||||
@IonicPage({ segment: 'addon-calendar-edit-event' })
|
||||
@Component({
|
||||
selector: 'page-addon-calendar-edit-event',
|
||||
templateUrl: 'edit-event.html',
|
||||
})
|
||||
export class AddonCalendarEditEventPage implements OnInit {
|
||||
|
||||
@ViewChild(CoreRichTextEditorComponent) descriptionEditor: CoreRichTextEditorComponent;
|
||||
|
||||
title: string;
|
||||
dateFormat: string;
|
||||
component = AddonCalendarProvider.COMPONENT;
|
||||
loaded = false;
|
||||
hasOffline = false;
|
||||
eventTypes = [];
|
||||
categories = [];
|
||||
courses = [];
|
||||
groups = [];
|
||||
loadingGroups = false;
|
||||
courseGroupSet = false;
|
||||
advanced = false;
|
||||
errors: any;
|
||||
|
||||
// Form variables.
|
||||
eventForm: FormGroup;
|
||||
eventTypeControl: FormControl;
|
||||
groupControl: FormControl;
|
||||
descriptionControl: FormControl;
|
||||
|
||||
protected eventId: number;
|
||||
protected courseId: number;
|
||||
protected originalData: any;
|
||||
protected currentSite: CoreSite;
|
||||
protected types: any; // Object with the supported types.
|
||||
protected showAll: boolean;
|
||||
|
||||
constructor(navParams: NavParams,
|
||||
private navCtrl: NavController,
|
||||
private translate: TranslateService,
|
||||
private domUtils: CoreDomUtilsProvider,
|
||||
private timeUtils: CoreTimeUtilsProvider,
|
||||
private eventsProvider: CoreEventsProvider,
|
||||
private groupsProvider: CoreGroupsProvider,
|
||||
sitesProvider: CoreSitesProvider,
|
||||
private coursesProvider: CoreCoursesProvider,
|
||||
private utils: CoreUtilsProvider,
|
||||
private calendarProvider: AddonCalendarProvider,
|
||||
private calendarHelper: AddonCalendarHelperProvider,
|
||||
private fb: FormBuilder,
|
||||
@Optional() private svComponent: CoreSplitViewComponent) {
|
||||
|
||||
this.eventId = navParams.get('eventId');
|
||||
this.courseId = navParams.get('courseId');
|
||||
this.title = this.eventId ? 'addon.calendar.editevent' : 'addon.calendar.newevent';
|
||||
|
||||
this.currentSite = sitesProvider.getCurrentSite();
|
||||
this.errors = {
|
||||
required: this.translate.instant('core.required')
|
||||
};
|
||||
|
||||
// Calculate format to use. ion-datetime doesn't support escaping characters ([]), so we remove them.
|
||||
this.dateFormat = this.timeUtils.convertPHPToMoment(this.translate.instant('core.strftimedatetimeshort'))
|
||||
.replace(/[\[\]]/g, '');
|
||||
|
||||
// Initialize form variables.
|
||||
this.eventForm = new FormGroup({});
|
||||
this.eventTypeControl = this.fb.control('', Validators.required);
|
||||
this.groupControl = this.fb.control('');
|
||||
this.descriptionControl = this.fb.control('');
|
||||
|
||||
this.eventForm.addControl('name', this.fb.control('', Validators.required));
|
||||
this.eventForm.addControl('timestart', this.fb.control(new Date().toISOString(), Validators.required));
|
||||
this.eventForm.addControl('eventtype', this.eventTypeControl);
|
||||
this.eventForm.addControl('categoryid', this.fb.control(''));
|
||||
this.eventForm.addControl('courseid', this.fb.control(this.courseId));
|
||||
this.eventForm.addControl('groupcourseid', this.fb.control(''));
|
||||
this.eventForm.addControl('groupid', this.groupControl);
|
||||
this.eventForm.addControl('description', this.descriptionControl);
|
||||
this.eventForm.addControl('location', this.fb.control(''));
|
||||
this.eventForm.addControl('duration', this.fb.control(0));
|
||||
this.eventForm.addControl('timedurationuntil', this.fb.control(new Date().toISOString()));
|
||||
this.eventForm.addControl('timedurationminutes', this.fb.control(''));
|
||||
this.eventForm.addControl('repeat', this.fb.control(false));
|
||||
this.eventForm.addControl('repeats', this.fb.control('1'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Component being initialized.
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this.fetchData().finally(() => {
|
||||
this.originalData = this.utils.clone(this.eventForm.value);
|
||||
this.loaded = true;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the data needed to render the form.
|
||||
*
|
||||
* @return {Promise<any>} Promise resolved when done.
|
||||
*/
|
||||
protected fetchData(): Promise<any> {
|
||||
let accessInfo;
|
||||
|
||||
// Get access info.
|
||||
return this.calendarProvider.getAccessInformation().then((info) => {
|
||||
accessInfo = info;
|
||||
|
||||
return this.calendarProvider.getAllowedEventTypes();
|
||||
}).then((types) => {
|
||||
this.types = types;
|
||||
|
||||
const promises = [],
|
||||
eventTypes = this.calendarHelper.getEventTypeOptions(types);
|
||||
|
||||
if (!eventTypes.length) {
|
||||
return Promise.reject(this.translate.instant('addon.calendar.nopermissiontoupdatecalendar'));
|
||||
}
|
||||
|
||||
if (types.category) {
|
||||
// Get the categories.
|
||||
promises.push(this.coursesProvider.getCategories(0, true).then((cats) => {
|
||||
this.categories = cats;
|
||||
}));
|
||||
}
|
||||
|
||||
this.showAll = this.utils.isTrueOrOne(this.currentSite.getStoredConfig('calendar_adminseesall')) &&
|
||||
accessInfo.canmanageentries;
|
||||
|
||||
if (types.course || types.groups) {
|
||||
// Get the courses.
|
||||
const promise = this.showAll ? this.coursesProvider.getCoursesByField() : this.coursesProvider.getUserCourses();
|
||||
|
||||
promises.push(promise.then((courses) => {
|
||||
if (this.showAll) {
|
||||
// Remove site home from the list of courses.
|
||||
const siteHomeId = this.currentSite.getSiteHomeId();
|
||||
courses = courses.filter((course) => {
|
||||
return course.id != siteHomeId;
|
||||
});
|
||||
}
|
||||
|
||||
// Sort courses by name.
|
||||
this.courses = courses.sort((a, b) => {
|
||||
const compareA = a.fullname.toLowerCase(),
|
||||
compareB = b.fullname.toLowerCase();
|
||||
|
||||
return compareA.localeCompare(compareB);
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
return Promise.all(promises).then(() => {
|
||||
// Set event types. If course is allowed, select it first.
|
||||
if (types.course) {
|
||||
this.eventTypeControl.setValue(AddonCalendarProvider.TYPE_COURSE);
|
||||
} else {
|
||||
this.eventTypeControl.setValue(eventTypes[0].value);
|
||||
}
|
||||
|
||||
this.eventTypes = eventTypes;
|
||||
});
|
||||
|
||||
}).catch((error) => {
|
||||
this.domUtils.showErrorModalDefault(error, 'Error getting data.');
|
||||
this.originalData = null; // Avoid asking for confirmation.
|
||||
this.navCtrl.pop();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Pull to refresh.
|
||||
*
|
||||
* @param {any} refresher Refresher.
|
||||
*/
|
||||
refreshData(refresher: any): void {
|
||||
const promises = [
|
||||
this.calendarProvider.invalidateAccessInformation(this.courseId),
|
||||
this.calendarProvider.invalidateAllowedEventTypes(this.courseId)
|
||||
];
|
||||
|
||||
if (this.types) {
|
||||
if (this.types.category) {
|
||||
promises.push(this.coursesProvider.invalidateCategories(0, true));
|
||||
}
|
||||
if (this.types.course || this.types.groups) {
|
||||
if (this.showAll) {
|
||||
promises.push(this.coursesProvider.invalidateCoursesByField());
|
||||
} else {
|
||||
promises.push(this.coursesProvider.invalidateUserCourses());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Promise.all(promises).finally(() => {
|
||||
this.fetchData().finally(() => {
|
||||
refresher.complete();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* A course was selected, get its groups.
|
||||
*
|
||||
* @param {number} courseId Course ID.
|
||||
*/
|
||||
groupCourseSelected(courseId: number): void {
|
||||
if (!courseId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const modal = this.domUtils.showModalLoading();
|
||||
this.loadingGroups = true;
|
||||
|
||||
this.groupsProvider.getUserGroupsInCourse(courseId).then((groups) => {
|
||||
this.groups = groups;
|
||||
this.courseGroupSet = true;
|
||||
this.groupControl.setValue('');
|
||||
}).catch((error) => {
|
||||
this.domUtils.showErrorModalDefault(error, 'Error getting data.');
|
||||
}).finally(() => {
|
||||
this.loadingGroups = false;
|
||||
modal.dismiss();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Show or hide advanced form fields.
|
||||
*/
|
||||
toggleAdvanced(): void {
|
||||
this.advanced = !this.advanced;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the event.
|
||||
*/
|
||||
submit(): void {
|
||||
// Validate data.
|
||||
const formData = this.eventForm.value,
|
||||
timeStartDate = new Date(formData.timestart),
|
||||
timeUntilDate = new Date(formData.timedurationuntil),
|
||||
timeDurationMinutes = parseInt(formData.timedurationminutes || '', 10);
|
||||
let error;
|
||||
|
||||
if (formData.eventtype == AddonCalendarProvider.TYPE_COURSE && !formData.courseid) {
|
||||
error = 'core.selectacourse';
|
||||
} else if (formData.eventtype == AddonCalendarProvider.TYPE_GROUP && !formData.groupcourseid) {
|
||||
error = 'core.selectacourse';
|
||||
} else if (formData.eventtype == AddonCalendarProvider.TYPE_GROUP && !formData.groupid) {
|
||||
error = 'core.selectagroup';
|
||||
} else if (formData.eventtype == AddonCalendarProvider.TYPE_CATEGORY && !formData.categoryid) {
|
||||
error = 'core.selectacategory';
|
||||
} else if (formData.duration == 1 && timeStartDate.getTime() > timeUntilDate.getTime()) {
|
||||
error = 'addon.calendar.invalidtimedurationuntil';
|
||||
} else if (formData.duration == 2 && (isNaN(timeDurationMinutes) || timeDurationMinutes < 1)) {
|
||||
error = 'addon.calendar.invalidtimedurationminutes';
|
||||
}
|
||||
|
||||
if (error) {
|
||||
// Show error and stop.
|
||||
this.domUtils.showErrorModal(this.translate.instant(error));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Format the data to send.
|
||||
const data: any = {
|
||||
name: formData.name,
|
||||
eventtype: formData.eventtype,
|
||||
timestart: Math.floor(timeStartDate.getTime() / 1000),
|
||||
description: {
|
||||
text: formData.description,
|
||||
format: 1
|
||||
},
|
||||
location: formData.location,
|
||||
duration: formData.duration,
|
||||
repeat: formData.repeat
|
||||
};
|
||||
|
||||
if (formData.eventtype == AddonCalendarProvider.TYPE_COURSE) {
|
||||
data.courseid = formData.courseid;
|
||||
} else if (formData.eventtype == AddonCalendarProvider.TYPE_GROUP) {
|
||||
data.groupcourseid = formData.groupcourseid;
|
||||
data.groupid = formData.groupid;
|
||||
} else if (formData.eventtype == AddonCalendarProvider.TYPE_CATEGORY) {
|
||||
data.categoryid = formData.categoryid;
|
||||
}
|
||||
|
||||
if (formData.duration == 1) {
|
||||
data.timedurationuntil = Math.floor(timeUntilDate.getTime() / 1000);
|
||||
} else if (formData.duration == 2) {
|
||||
data.timedurationminutes = formData.timedurationminutes;
|
||||
}
|
||||
|
||||
if (formData.repeat) {
|
||||
data.repeats = formData.repeats;
|
||||
}
|
||||
|
||||
// Send the data.
|
||||
const modal = this.domUtils.showModalLoading('core.sending');
|
||||
|
||||
this.calendarProvider.submitEvent(this.eventId, data).then((event) => {
|
||||
this.returnToList(event);
|
||||
}).catch((error) => {
|
||||
this.domUtils.showErrorModalDefault(error, 'Error sending data.');
|
||||
}).finally(() => {
|
||||
modal.dismiss();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience function to update or return to event list depending on device.
|
||||
*
|
||||
* @param {number} [event] Event.
|
||||
*/
|
||||
protected returnToList(event?: any): void {
|
||||
const data: any = {
|
||||
event: event
|
||||
};
|
||||
this.eventsProvider.trigger(AddonCalendarProvider.NEW_EVENT_EVENT, data, this.currentSite.getId());
|
||||
|
||||
if (this.svComponent && this.svComponent.isOn()) {
|
||||
// Empty form.
|
||||
this.hasOffline = false;
|
||||
this.eventForm.reset(this.originalData);
|
||||
this.originalData = this.utils.clone(this.eventForm.value);
|
||||
} else {
|
||||
this.originalData = null; // Avoid asking for confirmation.
|
||||
this.navCtrl.pop();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Discard an offline saved discussion.
|
||||
*/
|
||||
discard(): void {
|
||||
this.domUtils.showConfirm(this.translate.instant('core.areyousure')).then(() => {
|
||||
// @todo.
|
||||
}).catch(() => {
|
||||
// Cancelled.
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if we can leave the page or not.
|
||||
*
|
||||
* @return {boolean|Promise<void>} Resolved if we can leave it, rejected if not.
|
||||
*/
|
||||
ionViewCanLeave(): boolean | Promise<void> {
|
||||
|
||||
if (this.calendarHelper.hasEventDataChanged(this.eventForm.value, this.originalData)) {
|
||||
// Show confirmation if some data has been modified.
|
||||
return this.domUtils.showConfirm(this.translate.instant('core.confirmcanceledit'));
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -40,5 +40,12 @@
|
|||
|
||||
<core-infinite-loading [enabled]="canLoadMore" (action)="loadMoreEvents($event)" [error]="loadMoreError"></core-infinite-loading>
|
||||
</core-loading>
|
||||
|
||||
<!-- Create a calendar event. -->
|
||||
<ion-fab core-fab bottom end *ngIf="canCreate">
|
||||
<button ion-fab (click)="openCreate()" [attr.aria-label]="'addon.calendar.newevent' | translate">
|
||||
<ion-icon name="add"></ion-icon>
|
||||
</button>
|
||||
</ion-fab>
|
||||
</ion-content>
|
||||
</core-split-view>
|
|
@ -54,6 +54,7 @@ export class AddonCalendarListPage implements OnDestroy {
|
|||
protected obsDefaultTimeChange: any;
|
||||
protected eventId: number;
|
||||
protected preSelectedCourseId: number;
|
||||
protected newEventObserver: any;
|
||||
|
||||
courses: any[];
|
||||
eventsLoaded = false;
|
||||
|
@ -65,6 +66,7 @@ export class AddonCalendarListPage implements OnDestroy {
|
|||
filter = {
|
||||
course: this.allCourses
|
||||
};
|
||||
canCreate = false;
|
||||
|
||||
constructor(private translate: TranslateService, private calendarProvider: AddonCalendarProvider, navParams: NavParams,
|
||||
private domUtils: CoreDomUtilsProvider, private coursesProvider: CoreCoursesProvider, private utils: CoreUtilsProvider,
|
||||
|
@ -74,6 +76,7 @@ export class AddonCalendarListPage implements OnDestroy {
|
|||
|
||||
this.siteHomeId = sitesProvider.getCurrentSite().getSiteHomeId();
|
||||
this.notificationsEnabled = localNotificationsProvider.isAvailable();
|
||||
|
||||
if (this.notificationsEnabled) {
|
||||
// Re-schedule events if default time changes.
|
||||
this.obsDefaultTimeChange = eventsProvider.on(AddonCalendarProvider.DEFAULT_NOTIFICATION_TIME_CHANGED, () => {
|
||||
|
@ -83,6 +86,30 @@ export class AddonCalendarListPage implements OnDestroy {
|
|||
|
||||
this.eventId = navParams.get('eventId') || false;
|
||||
this.preSelectedCourseId = navParams.get('courseId') || null;
|
||||
|
||||
// Listen for events added. When an event is added, we reload the data.
|
||||
this.newEventObserver = eventsProvider.on(AddonCalendarProvider.NEW_EVENT_EVENT, (data) => {
|
||||
if (data && data.event) {
|
||||
if (this.splitviewCtrl.isOn()) {
|
||||
// Discussion added, clear details page.
|
||||
this.splitviewCtrl.emptyDetails();
|
||||
}
|
||||
|
||||
this.eventsLoaded = false;
|
||||
this.refreshEvents(false).finally(() => {
|
||||
this.eventsLoaded = true;
|
||||
|
||||
// In tablet mode try to open the event.
|
||||
if (this.splitviewCtrl.isOn()) {
|
||||
if (data.event.id) {
|
||||
this.gotoEvent(data.event.id);
|
||||
} else {
|
||||
// It's an offline event.
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}, sitesProvider.getCurrentSiteId());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -114,8 +141,19 @@ export class AddonCalendarListPage implements OnDestroy {
|
|||
this.daysLoaded = 0;
|
||||
this.emptyEventsTimes = 0;
|
||||
|
||||
const promises = [];
|
||||
|
||||
if (this.calendarProvider.canEditEventsInSite()) {
|
||||
// Site allows creating events. Check if the user has permissions to do so.
|
||||
promises.push(this.calendarProvider.getAllowedEventTypes().then((types) => {
|
||||
this.canCreate = Object.keys(types).length > 0;
|
||||
}).catch(() => {
|
||||
this.canCreate = false;
|
||||
}));
|
||||
}
|
||||
|
||||
// Load courses for the popover.
|
||||
return this.coursesProvider.getUserCourses(false).then((courses) => {
|
||||
promises.push(this.coursesProvider.getUserCourses(false).then((courses) => {
|
||||
// Add "All courses".
|
||||
courses.unshift(this.allCourses);
|
||||
this.courses = courses;
|
||||
|
@ -127,7 +165,9 @@ export class AddonCalendarListPage implements OnDestroy {
|
|||
}
|
||||
|
||||
return this.fetchEvents(refresh);
|
||||
});
|
||||
}));
|
||||
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -308,21 +348,23 @@ export class AddonCalendarListPage implements OnDestroy {
|
|||
/**
|
||||
* Refresh the events.
|
||||
*
|
||||
* @param {any} refresher Refresher.
|
||||
* @param {any} [refresher] Refresher.
|
||||
* @return {Promise<any>} Promise resolved when done.
|
||||
*/
|
||||
refreshEvents(refresher: any): void {
|
||||
refreshEvents(refresher?: any): Promise<any> {
|
||||
const promises = [];
|
||||
|
||||
promises.push(this.calendarProvider.invalidateEventsList());
|
||||
promises.push(this.calendarProvider.invalidateAllowedEventTypes());
|
||||
|
||||
if (this.categoriesRetrieved) {
|
||||
promises.push(this.coursesProvider.invalidateCategories(0, true));
|
||||
this.categoriesRetrieved = false;
|
||||
}
|
||||
|
||||
Promise.all(promises).finally(() => {
|
||||
this.fetchData(true).finally(() => {
|
||||
refresher.complete();
|
||||
return Promise.all(promises).finally(() => {
|
||||
return this.fetchData(true).finally(() => {
|
||||
refresher && refresher.complete();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -384,6 +426,18 @@ export class AddonCalendarListPage implements OnDestroy {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Open page to create an event.
|
||||
*/
|
||||
openCreate(): void {
|
||||
const params: any = {};
|
||||
if (this.filter.course.id != this.allCourses.id) {
|
||||
params.courseId = this.filter.course.id;
|
||||
}
|
||||
|
||||
this.splitviewCtrl.push('AddonCalendarEditEventPage', params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Open calendar events settings.
|
||||
*/
|
||||
|
@ -406,5 +460,6 @@ export class AddonCalendarListPage implements OnDestroy {
|
|||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.obsDefaultTimeChange && this.obsDefaultTimeChange.off();
|
||||
this.newEventObserver && this.newEventObserver.off();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ import { CoreSitesProvider, CoreSiteSchema } from '@providers/sites';
|
|||
import { CoreSite } from '@classes/site';
|
||||
import { CoreCoursesProvider } from '@core/courses/providers/courses';
|
||||
import { CoreTimeUtilsProvider } from '@providers/utils/time';
|
||||
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||
import { CoreGroupsProvider } from '@providers/groups';
|
||||
import { CoreConstants } from '@core/constants';
|
||||
import { CoreLocalNotificationsProvider } from '@providers/local-notifications';
|
||||
|
@ -35,6 +36,12 @@ export class AddonCalendarProvider {
|
|||
static DEFAULT_NOTIFICATION_TIME_CHANGED = 'AddonCalendarDefaultNotificationTimeChangedEvent';
|
||||
static DEFAULT_NOTIFICATION_TIME_SETTING = 'mmaCalendarDefaultNotifTime';
|
||||
static DEFAULT_NOTIFICATION_TIME = 60;
|
||||
static NEW_EVENT_EVENT = 'addon_calendar_new_event';
|
||||
static TYPE_CATEGORY = 'category';
|
||||
static TYPE_COURSE = 'course';
|
||||
static TYPE_GROUP = 'group';
|
||||
static TYPE_SITE = 'site';
|
||||
static TYPE_USER = 'user';
|
||||
protected ROOT_CACHE_KEY = 'mmaCalendar:';
|
||||
|
||||
// Variables for database.
|
||||
|
@ -206,11 +213,37 @@ export class AddonCalendarProvider {
|
|||
|
||||
constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private groupsProvider: CoreGroupsProvider,
|
||||
private coursesProvider: CoreCoursesProvider, private timeUtils: CoreTimeUtilsProvider,
|
||||
private localNotificationsProvider: CoreLocalNotificationsProvider, private configProvider: CoreConfigProvider) {
|
||||
private localNotificationsProvider: CoreLocalNotificationsProvider, private configProvider: CoreConfigProvider,
|
||||
private utils: CoreUtilsProvider) {
|
||||
this.logger = logger.getInstance('AddonCalendarProvider');
|
||||
this.sitesProvider.registerSiteSchema(this.siteSchema);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a certain site allows creating and editing events.
|
||||
*
|
||||
* @param {string} [siteId] Site Id. If not defined, use current site.
|
||||
* @return {Promise<boolean>} Promise resolved with true if can create/edit.
|
||||
*/
|
||||
canEditEvents(siteId?: string): Promise<boolean> {
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
return this.canEditEventsInSite(site);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a certain site allows creating and editing events.
|
||||
*
|
||||
* @param {CoreSite} [site] Site. If not defined, use current site.
|
||||
* @return {boolean} Whether events can be created and edited.
|
||||
*/
|
||||
canEditEventsInSite(site?: CoreSite): boolean {
|
||||
site = site || this.sitesProvider.getCurrentSite();
|
||||
|
||||
// The WS to create/edit events requires a fix that was integrated in 3.7.1.
|
||||
return site.isVersionGreaterEqualThan('3.7.1');
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes expired events from local DB.
|
||||
*
|
||||
|
@ -255,6 +288,39 @@ export class AddonCalendarProvider {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get access information for a calendar (either course calendar or site calendar).
|
||||
*
|
||||
* @param {number} [courseId] Course ID. If not defined, site calendar.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Promise resolved with object with access information.
|
||||
* @since 3.7
|
||||
*/
|
||||
getAccessInformation(courseId?: number, siteId?: string): Promise<any> {
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
const params: any = {},
|
||||
preSets = {
|
||||
cacheKey: this.getAccessInformationCacheKey(courseId)
|
||||
};
|
||||
|
||||
if (courseId) {
|
||||
params.courseid = courseId;
|
||||
}
|
||||
|
||||
return site.read('core_calendar_get_calendar_access_information', params, preSets);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cache key for calendar access information WS calls.
|
||||
*
|
||||
* @param {number} [courseId] Course ID.
|
||||
* @return {string} Cache key.
|
||||
*/
|
||||
protected getAccessInformationCacheKey(courseId?: number): string {
|
||||
return this.ROOT_CACHE_KEY + 'accessInformation:' + (courseId || 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all calendar events from local Db.
|
||||
*
|
||||
|
@ -267,6 +333,50 @@ export class AddonCalendarProvider {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the type of events a user can create (either course calendar or site calendar).
|
||||
*
|
||||
* @param {number} [courseId] Course ID. If not defined, site calendar.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Promise resolved with an object indicating the types.
|
||||
* @since 3.7
|
||||
*/
|
||||
getAllowedEventTypes(courseId?: number, siteId?: string): Promise<any> {
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
const params: any = {},
|
||||
preSets = {
|
||||
cacheKey: this.getAllowedEventTypesCacheKey(courseId)
|
||||
};
|
||||
|
||||
if (courseId) {
|
||||
params.courseid = courseId;
|
||||
}
|
||||
|
||||
return site.read('core_calendar_get_allowed_event_types', params, preSets).then((response) => {
|
||||
// Convert the array to an object.
|
||||
const result = {};
|
||||
|
||||
if (response.allowedeventtypes) {
|
||||
response.allowedeventtypes.map((type) => {
|
||||
result[type] = true;
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cache key for calendar allowed event types WS calls.
|
||||
*
|
||||
* @param {number} [courseId] Course ID.
|
||||
* @return {string} Cache key.
|
||||
*/
|
||||
protected getAllowedEventTypesCacheKey(courseId?: number): string {
|
||||
return this.ROOT_CACHE_KEY + 'allowedEventTypes:' + (courseId || 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the configured default notification time.
|
||||
*
|
||||
|
@ -504,6 +614,32 @@ export class AddonCalendarProvider {
|
|||
return this.getEventsListPrefixCacheKey() + daysToStart + ':' + daysInterval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidates access information.
|
||||
*
|
||||
* @param {number} [courseId] Course ID. If not defined, site calendar.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Promise resolved when the data is invalidated.
|
||||
*/
|
||||
invalidateAccessInformation(courseId?: number, siteId?: string): Promise<any> {
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
return site.invalidateWsCacheForKey(this.getAccessInformationCacheKey(courseId));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidates allowed event types.
|
||||
*
|
||||
* @param {number} [courseId] Course ID. If not defined, site calendar.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Promise resolved when the data is invalidated.
|
||||
*/
|
||||
invalidateAllowedEventTypes(courseId?: number, siteId?: string): Promise<any> {
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
return site.invalidateWsCacheForKey(this.getAllowedEventTypesCacheKey(courseId));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidates events list and all the single events and related info.
|
||||
*
|
||||
|
@ -780,4 +916,35 @@ export class AddonCalendarProvider {
|
|||
}));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit an event, either to create it or to edit it.
|
||||
*
|
||||
* @param {number} eventId ID of the event. If undefined/null, create a new event.
|
||||
* @param {any} formData Form data.
|
||||
* @param {string} [siteId] Site ID. If not provided, current site.
|
||||
* @return {Promise<any>} Promise resolved when done.
|
||||
*/
|
||||
submitEvent(eventId: number, formData: any, siteId?: string): Promise<any> {
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
// Add data that is "hidden" in web.
|
||||
formData.id = eventId || 0;
|
||||
formData.userid = site.getUserId();
|
||||
formData.visible = 1;
|
||||
formData.instance = 0;
|
||||
formData['_qf__core_calendar_local_event_forms_create'] = 1;
|
||||
|
||||
const params = {
|
||||
formdata: this.utils.objectToGetParams(formData)
|
||||
};
|
||||
|
||||
return site.write('core_calendar_submit_create_update_form', params).then((result) => {
|
||||
if (result.validationerror) {
|
||||
return Promise.reject(null);
|
||||
}
|
||||
|
||||
return result.event;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { CoreLoggerProvider } from '@providers/logger';
|
||||
import { CoreCourseProvider } from '@core/course/providers/course';
|
||||
import { AddonCalendarProvider } from './calendar';
|
||||
|
||||
/**
|
||||
* Service that provides some features regarding lists of courses and categories.
|
||||
|
@ -47,4 +48,73 @@ export class AddonCalendarHelperProvider {
|
|||
e.moduleIcon = e.icon;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get options (name & value) for each allowed event type.
|
||||
*
|
||||
* @param {any} eventTypes Result of getAllowedEventTypes.
|
||||
* @return {{name: string, value: string}[]} Options.
|
||||
*/
|
||||
getEventTypeOptions(eventTypes: any): {name: string, value: string}[] {
|
||||
const options = [];
|
||||
|
||||
if (eventTypes.user) {
|
||||
options.push({name: 'core.user', value: AddonCalendarProvider.TYPE_USER});
|
||||
}
|
||||
if (eventTypes.group) {
|
||||
options.push({name: 'core.group', value: AddonCalendarProvider.TYPE_GROUP});
|
||||
}
|
||||
if (eventTypes.course) {
|
||||
options.push({name: 'core.course', value: AddonCalendarProvider.TYPE_COURSE});
|
||||
}
|
||||
if (eventTypes.category) {
|
||||
options.push({name: 'core.category', value: AddonCalendarProvider.TYPE_CATEGORY});
|
||||
}
|
||||
if (eventTypes.site) {
|
||||
options.push({name: 'core.site', value: AddonCalendarProvider.TYPE_SITE});
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the data of an event has changed.
|
||||
*
|
||||
* @param {any} data Current data.
|
||||
* @param {any} [original] Original data.
|
||||
* @return {boolean} True if data has changed, false otherwise.
|
||||
*/
|
||||
hasEventDataChanged(data: any, original?: any): boolean {
|
||||
if (!original) {
|
||||
// There is no original data, assume it hasn't changed.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check the fields that don't depend on any other.
|
||||
if (data.name != original.name || data.timestart != original.timestart || data.eventtype != original.eventtype ||
|
||||
data.description != original.description || data.location != original.location ||
|
||||
data.duration != original.duration || data.repeat != original.repeat) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check data that depends on eventtype.
|
||||
if ((data.eventtype == AddonCalendarProvider.TYPE_CATEGORY && data.categoryid != original.categoryid) ||
|
||||
(data.eventtype == AddonCalendarProvider.TYPE_COURSE && data.courseid != original.courseid) ||
|
||||
(data.eventtype == AddonCalendarProvider.TYPE_GROUP && data.groupcourseid != original.groupcourseid &&
|
||||
data.groupid != original.groupid)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check data that depends on duration.
|
||||
if ((data.duration == 1 && data.timedurationuntil != original.timedurationuntil) ||
|
||||
(data.duration == 2 && data.timedurationminutes != original.timedurationminutes)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (data.repeat && data.repeats != original.repeats) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -87,13 +87,26 @@
|
|||
"addon.calendar.calendarevents": "Calendar events",
|
||||
"addon.calendar.calendarreminders": "Calendar reminders",
|
||||
"addon.calendar.defaultnotificationtime": "Default notification time",
|
||||
"addon.calendar.durationminutes": "Duration in minutes",
|
||||
"addon.calendar.durationnone": "Without duration",
|
||||
"addon.calendar.durationuntil": "Until",
|
||||
"addon.calendar.editevent": "Editing event",
|
||||
"addon.calendar.errorloadevent": "Error loading event.",
|
||||
"addon.calendar.errorloadevents": "Error loading events.",
|
||||
"addon.calendar.eventduration": "Duration",
|
||||
"addon.calendar.eventendtime": "End time",
|
||||
"addon.calendar.eventname": "Event title",
|
||||
"addon.calendar.eventstarttime": "Start time",
|
||||
"addon.calendar.eventtype": "Event type",
|
||||
"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.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",
|
||||
"addon.calendar.reminders": "Reminders",
|
||||
"addon.calendar.repeatevent": "Repeat this event",
|
||||
"addon.calendar.repeatweeksl": "Repeat weekly, creating altogether",
|
||||
"addon.calendar.setnewreminder": "Set a new reminder",
|
||||
"addon.calendar.typecategory": "Category event",
|
||||
"addon.calendar.typeclose": "Close event",
|
||||
|
@ -1328,6 +1341,7 @@
|
|||
"core.course.warningmanualcompletionmodified": "The manual completion of an activity was modified on the site.",
|
||||
"core.course.warningofflinemanualcompletiondeleted": "Some offline manual completion of course '{{name}}' has been deleted. {{error}}",
|
||||
"core.coursedetails": "Course details",
|
||||
"core.coursenogroups": "This course doesn't have any group.",
|
||||
"core.courses.addtofavourites": "Star this course",
|
||||
"core.courses.allowguests": "This course allows guest users to enter",
|
||||
"core.courses.availablecourses": "Available courses",
|
||||
|
@ -1457,6 +1471,7 @@
|
|||
"core.grades.range": "Range",
|
||||
"core.grades.rank": "Rank",
|
||||
"core.grades.weight": "Weight",
|
||||
"core.group": "Group",
|
||||
"core.groupsseparate": "Separate groups",
|
||||
"core.groupsvisible": "Visible groups",
|
||||
"core.hasdatatosync": "This {{$a}} has offline data to be synchronised.",
|
||||
|
@ -1624,6 +1639,7 @@
|
|||
"core.nopermissionerror": "Sorry, but you do not currently have permissions to do that",
|
||||
"core.nopermissions": "Sorry, but you do not currently have permissions to do that ({{$a}}).",
|
||||
"core.noresults": "No results",
|
||||
"core.noselection": "No selection",
|
||||
"core.notapplicable": "n/a",
|
||||
"core.notenrolledprofile": "This profile is not available because this user is not enrolled in this course.",
|
||||
"core.notice": "Notice",
|
||||
|
@ -1692,6 +1708,9 @@
|
|||
"core.sec": "sec",
|
||||
"core.secs": "secs",
|
||||
"core.seemoredetail": "Click here to see more detail",
|
||||
"core.selectacategory": "Please select a category",
|
||||
"core.selectacourse": "Select a course",
|
||||
"core.selectagroup": "Select a group",
|
||||
"core.send": "Send",
|
||||
"core.sending": "Sending",
|
||||
"core.serverconnection": "Error connecting to the server",
|
||||
|
@ -1760,6 +1779,7 @@
|
|||
"core.sharedfiles.sharedfiles": "Shared files",
|
||||
"core.sharedfiles.successstorefile": "File successfully stored. Select the file to upload to your private files or use in an activity.",
|
||||
"core.show": "Show",
|
||||
"core.showless": "Show less...",
|
||||
"core.showmore": "Show more...",
|
||||
"core.site": "Site",
|
||||
"core.sitehome.sitehome": "Site home",
|
||||
|
@ -1808,6 +1828,7 @@
|
|||
"core.unlimited": "Unlimited",
|
||||
"core.unzipping": "Unzipping",
|
||||
"core.upgraderunning": "Site is being upgraded, please retry later.",
|
||||
"core.user": "User",
|
||||
"core.user.address": "Address",
|
||||
"core.user.city": "City/town",
|
||||
"core.user.contact": "Contact",
|
||||
|
|
|
@ -51,6 +51,7 @@
|
|||
"copiedtoclipboard": "Text copied to clipboard",
|
||||
"course": "Course",
|
||||
"coursedetails": "Course details",
|
||||
"coursenogroups": "This course doesn't have any group.",
|
||||
"currentdevice": "Current device",
|
||||
"datastoredoffline": "Data stored in the device because it couldn't be sent. It will be sent automatically later.",
|
||||
"date": "Date",
|
||||
|
@ -104,6 +105,7 @@
|
|||
"forcepasswordchangenotice": "You must change your password to proceed.",
|
||||
"fulllistofcourses": "All courses",
|
||||
"fullnameandsitename": "{{fullname}} ({{sitename}})",
|
||||
"group": "Group",
|
||||
"groupsseparate": "Separate groups",
|
||||
"groupsvisible": "Visible groups",
|
||||
"hasdatatosync": "This {{$a}} has offline data to be synchronised.",
|
||||
|
@ -174,6 +176,7 @@
|
|||
"nopermissionerror": "Sorry, but you do not currently have permissions to do that",
|
||||
"nopermissions": "Sorry, but you do not currently have permissions to do that ({{$a}}).",
|
||||
"noresults": "No results",
|
||||
"noselection": "No selection",
|
||||
"notapplicable": "n/a",
|
||||
"notenrolledprofile": "This profile is not available because this user is not enrolled in this course.",
|
||||
"notice": "Notice",
|
||||
|
@ -214,10 +217,14 @@
|
|||
"sec": "sec",
|
||||
"secs": "secs",
|
||||
"seemoredetail": "Click here to see more detail",
|
||||
"selectacategory": "Please select a category",
|
||||
"selectacourse": "Select a course",
|
||||
"selectagroup": "Select a group",
|
||||
"send": "Send",
|
||||
"sending": "Sending",
|
||||
"serverconnection": "Error connecting to the server",
|
||||
"show": "Show",
|
||||
"showless": "Show less...",
|
||||
"showmore": "Show more...",
|
||||
"site": "Site",
|
||||
"sitemaintenance": "The site is undergoing maintenance and is currently not available",
|
||||
|
@ -264,6 +271,7 @@
|
|||
"unlimited": "Unlimited",
|
||||
"unzipping": "Unzipping",
|
||||
"upgraderunning": "Site is being upgraded, please retry later.",
|
||||
"user": "User",
|
||||
"userdeleted": "This user account has been deleted",
|
||||
"userdetails": "User details",
|
||||
"usernotfullysetup": "User not fully set-up",
|
||||
|
|
|
@ -376,13 +376,15 @@ export class CoreUtilsProvider {
|
|||
}
|
||||
|
||||
/**
|
||||
* Flatten an object, moving subobjects' properties to the first level using dot notation. E.g.:
|
||||
* {a: {b: 1, c: 2}, d: 3} -> {'a.b': 1, 'a.c': 2, d: 3}
|
||||
* Flatten an object, moving subobjects' properties to the first level.
|
||||
* It supports 2 notations: dot notation and square brackets.
|
||||
* E.g.: {a: {b: 1, c: 2}, d: 3} -> {'a.b': 1, 'a.c': 2, d: 3}
|
||||
*
|
||||
* @param {object} obj Object to flatten.
|
||||
* @return {object} Flatten object.
|
||||
* @param {boolean} [useDotNotation] Whether to use dot notation '.' or square brackets '['.
|
||||
* @return {object} Flattened object.
|
||||
*/
|
||||
flattenObject(obj: object): object {
|
||||
flattenObject(obj: object, useDotNotation?: boolean): object {
|
||||
const toReturn = {};
|
||||
|
||||
for (const name in obj) {
|
||||
|
@ -398,7 +400,8 @@ export class CoreUtilsProvider {
|
|||
continue;
|
||||
}
|
||||
|
||||
toReturn[name + '.' + subName] = flatObject[subName];
|
||||
const newName = useDotNotation ? name + '.' + subName : name + '[' + subName + ']';
|
||||
toReturn[newName] = flatObject[subName];
|
||||
}
|
||||
} else {
|
||||
toReturn[name] = value;
|
||||
|
@ -1051,6 +1054,37 @@ export class CoreUtilsProvider {
|
|||
return mapped;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an object to a format of GET param. E.g.: {a: 1, b: 2} -> a=1&b=2
|
||||
*
|
||||
* @param {any} object Object to convert.
|
||||
* @param {boolean} [removeEmpty=true] Whether to remove params whose value is empty/null/undefined.
|
||||
* @return {string} GET params.
|
||||
*/
|
||||
objectToGetParams(object: any, removeEmpty: boolean = true): string {
|
||||
// First of all, flatten the object so all properties are in the first level.
|
||||
const flattened = this.flattenObject(object);
|
||||
let result = '',
|
||||
joinChar = '';
|
||||
|
||||
for (const name in flattened) {
|
||||
let value = flattened[name];
|
||||
|
||||
if (removeEmpty && (value === null || typeof value == 'undefined' || value === '')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (typeof value == 'boolean') {
|
||||
value = value ? 1 : 0;
|
||||
}
|
||||
|
||||
result += joinChar + name + '=' + value;
|
||||
joinChar = '&';
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a prefix to all the keys in an object.
|
||||
*
|
||||
|
|
Loading…
Reference in New Issue