commit
943a9044ad
|
@ -84,16 +84,30 @@
|
||||||
"addon.blog.showonlyyourentries": "local_moodlemobileapp",
|
"addon.blog.showonlyyourentries": "local_moodlemobileapp",
|
||||||
"addon.blog.siteblogheading": "blog",
|
"addon.blog.siteblogheading": "blog",
|
||||||
"addon.calendar.calendar": "calendar",
|
"addon.calendar.calendar": "calendar",
|
||||||
|
"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.defaultnotificationtime": "local_moodlemobileapp",
|
"addon.calendar.defaultnotificationtime": "local_moodlemobileapp",
|
||||||
|
"addon.calendar.durationminutes": "calendar",
|
||||||
|
"addon.calendar.durationnone": "calendar",
|
||||||
|
"addon.calendar.durationuntil": "calendar",
|
||||||
|
"addon.calendar.editevent": "calendar",
|
||||||
"addon.calendar.errorloadevent": "local_moodlemobileapp",
|
"addon.calendar.errorloadevent": "local_moodlemobileapp",
|
||||||
"addon.calendar.errorloadevents": "local_moodlemobileapp",
|
"addon.calendar.errorloadevents": "local_moodlemobileapp",
|
||||||
|
"addon.calendar.eventduration": "calendar",
|
||||||
"addon.calendar.eventendtime": "calendar",
|
"addon.calendar.eventendtime": "calendar",
|
||||||
|
"addon.calendar.eventname": "calendar",
|
||||||
"addon.calendar.eventstarttime": "calendar",
|
"addon.calendar.eventstarttime": "calendar",
|
||||||
|
"addon.calendar.eventtype": "calendar",
|
||||||
"addon.calendar.gotoactivity": "calendar",
|
"addon.calendar.gotoactivity": "calendar",
|
||||||
|
"addon.calendar.invalidtimedurationminutes": "calendar",
|
||||||
|
"addon.calendar.invalidtimedurationuntil": "calendar",
|
||||||
|
"addon.calendar.newevent": "calendar",
|
||||||
"addon.calendar.noevents": "local_moodlemobileapp",
|
"addon.calendar.noevents": "local_moodlemobileapp",
|
||||||
|
"addon.calendar.nopermissiontoupdatecalendar": "error",
|
||||||
"addon.calendar.reminders": "local_moodlemobileapp",
|
"addon.calendar.reminders": "local_moodlemobileapp",
|
||||||
|
"addon.calendar.repeatevent": "calendar",
|
||||||
|
"addon.calendar.repeatweeksl": "calendar",
|
||||||
"addon.calendar.setnewreminder": "local_moodlemobileapp",
|
"addon.calendar.setnewreminder": "local_moodlemobileapp",
|
||||||
"addon.calendar.typecategory": "calendar",
|
"addon.calendar.typecategory": "calendar",
|
||||||
"addon.calendar.typeclose": "calendar",
|
"addon.calendar.typeclose": "calendar",
|
||||||
|
@ -1333,6 +1347,7 @@
|
||||||
"core.course.warningmanualcompletionmodified": "local_moodlemobileapp",
|
"core.course.warningmanualcompletionmodified": "local_moodlemobileapp",
|
||||||
"core.course.warningofflinemanualcompletiondeleted": "local_moodlemobileapp",
|
"core.course.warningofflinemanualcompletiondeleted": "local_moodlemobileapp",
|
||||||
"core.coursedetails": "moodle",
|
"core.coursedetails": "moodle",
|
||||||
|
"core.coursenogroups": "local_moodlemobileapp",
|
||||||
"core.courses.addtofavourites": "block_myoverview",
|
"core.courses.addtofavourites": "block_myoverview",
|
||||||
"core.courses.allowguests": "enrol_guest",
|
"core.courses.allowguests": "enrol_guest",
|
||||||
"core.courses.availablecourses": "moodle",
|
"core.courses.availablecourses": "moodle",
|
||||||
|
@ -1463,6 +1478,7 @@
|
||||||
"core.grades.range": "grades",
|
"core.grades.range": "grades",
|
||||||
"core.grades.rank": "grades",
|
"core.grades.rank": "grades",
|
||||||
"core.grades.weight": "grades",
|
"core.grades.weight": "grades",
|
||||||
|
"core.group": "moodle",
|
||||||
"core.groupsseparate": "moodle",
|
"core.groupsseparate": "moodle",
|
||||||
"core.groupsvisible": "moodle",
|
"core.groupsvisible": "moodle",
|
||||||
"core.hasdatatosync": "local_moodlemobileapp",
|
"core.hasdatatosync": "local_moodlemobileapp",
|
||||||
|
@ -1698,6 +1714,9 @@
|
||||||
"core.sec": "moodle",
|
"core.sec": "moodle",
|
||||||
"core.secs": "moodle",
|
"core.secs": "moodle",
|
||||||
"core.seemoredetail": "survey",
|
"core.seemoredetail": "survey",
|
||||||
|
"core.selectacategory": "moodle",
|
||||||
|
"core.selectacourse": "moodle",
|
||||||
|
"core.selectagroup": "moodle",
|
||||||
"core.send": "message",
|
"core.send": "message",
|
||||||
"core.sending": "chat",
|
"core.sending": "chat",
|
||||||
"core.serverconnection": "error",
|
"core.serverconnection": "error",
|
||||||
|
@ -1766,6 +1785,7 @@
|
||||||
"core.sharedfiles.sharedfiles": "local_moodlemobileapp",
|
"core.sharedfiles.sharedfiles": "local_moodlemobileapp",
|
||||||
"core.sharedfiles.successstorefile": "local_moodlemobileapp",
|
"core.sharedfiles.successstorefile": "local_moodlemobileapp",
|
||||||
"core.show": "moodle",
|
"core.show": "moodle",
|
||||||
|
"core.showless": "form",
|
||||||
"core.showmore": "form",
|
"core.showmore": "form",
|
||||||
"core.site": "moodle",
|
"core.site": "moodle",
|
||||||
"core.sitehome.sitehome": "moodle",
|
"core.sitehome.sitehome": "moodle",
|
||||||
|
@ -1814,6 +1834,7 @@
|
||||||
"core.unlimited": "moodle",
|
"core.unlimited": "moodle",
|
||||||
"core.unzipping": "local_moodlemobileapp",
|
"core.unzipping": "local_moodlemobileapp",
|
||||||
"core.upgraderunning": "error",
|
"core.upgraderunning": "error",
|
||||||
|
"core.user": "moodle",
|
||||||
"core.user.address": "moodle",
|
"core.user.address": "moodle",
|
||||||
"core.user.city": "moodle",
|
"core.user.city": "moodle",
|
||||||
"core.user.contact": "local_moodlemobileapp",
|
"core.user.contact": "local_moodlemobileapp",
|
||||||
|
|
|
@ -14,9 +14,13 @@
|
||||||
|
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { AddonCalendarProvider } from './providers/calendar';
|
import { AddonCalendarProvider } from './providers/calendar';
|
||||||
|
import { AddonCalendarOfflineProvider } from './providers/calendar-offline';
|
||||||
import { AddonCalendarHelperProvider } from './providers/helper';
|
import { AddonCalendarHelperProvider } from './providers/helper';
|
||||||
|
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 { CoreMainMenuDelegate } from '@core/mainmenu/providers/delegate';
|
import { CoreMainMenuDelegate } from '@core/mainmenu/providers/delegate';
|
||||||
|
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';
|
||||||
|
@ -25,7 +29,9 @@ import { CoreUpdateManagerProvider } from '@providers/update-manager';
|
||||||
// List of providers (without handlers).
|
// List of providers (without handlers).
|
||||||
export const ADDON_CALENDAR_PROVIDERS: any[] = [
|
export const ADDON_CALENDAR_PROVIDERS: any[] = [
|
||||||
AddonCalendarProvider,
|
AddonCalendarProvider,
|
||||||
AddonCalendarHelperProvider
|
AddonCalendarOfflineProvider,
|
||||||
|
AddonCalendarHelperProvider,
|
||||||
|
AddonCalendarSyncProvider
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
@ -35,15 +41,21 @@ export const ADDON_CALENDAR_PROVIDERS: any[] = [
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
AddonCalendarProvider,
|
AddonCalendarProvider,
|
||||||
|
AddonCalendarOfflineProvider,
|
||||||
AddonCalendarHelperProvider,
|
AddonCalendarHelperProvider,
|
||||||
AddonCalendarMainMenuHandler
|
AddonCalendarSyncProvider,
|
||||||
|
AddonCalendarMainMenuHandler,
|
||||||
|
AddonCalendarSyncCronHandler
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
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) {
|
||||||
|
|
||||||
mainMenuDelegate.registerHandler(calendarHandler);
|
mainMenuDelegate.registerHandler(calendarHandler);
|
||||||
|
cronDelegate.register(syncHandler);
|
||||||
|
|
||||||
initDelegate.ready().then(() => {
|
initDelegate.ready().then(() => {
|
||||||
calendarProvider.scheduleAllSitesEventsNotifications();
|
calendarProvider.scheduleAllSitesEventsNotifications();
|
||||||
|
|
|
@ -1,15 +1,29 @@
|
||||||
{
|
{
|
||||||
"calendar": "Calendar",
|
"calendar": "Calendar",
|
||||||
|
"calendarevent": "Calendar event",
|
||||||
"calendarevents": "Calendar events",
|
"calendarevents": "Calendar events",
|
||||||
"calendarreminders": "Calendar reminders",
|
"calendarreminders": "Calendar reminders",
|
||||||
"defaultnotificationtime": "Default notification time",
|
"defaultnotificationtime": "Default notification time",
|
||||||
|
"durationminutes": "Duration in minutes",
|
||||||
|
"durationnone": "Without duration",
|
||||||
|
"durationuntil": "Until",
|
||||||
|
"editevent": "Editing event",
|
||||||
"errorloadevent": "Error loading event.",
|
"errorloadevent": "Error loading event.",
|
||||||
"errorloadevents": "Error loading events.",
|
"errorloadevents": "Error loading events.",
|
||||||
|
"eventduration": "Duration",
|
||||||
"eventendtime": "End time",
|
"eventendtime": "End time",
|
||||||
|
"eventname": "Event title",
|
||||||
"eventstarttime": "Start time",
|
"eventstarttime": "Start time",
|
||||||
|
"eventtype": "Event type",
|
||||||
"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.",
|
||||||
|
"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",
|
"noevents": "There are no events",
|
||||||
|
"nopermissiontoupdatecalendar": "Sorry, but you do not have permission to update the calendar event",
|
||||||
"reminders": "Reminders",
|
"reminders": "Reminders",
|
||||||
|
"repeatevent": "Repeat this event",
|
||||||
|
"repeatweeksl": "Repeat weekly, creating altogether",
|
||||||
"setnewreminder": "Set a new reminder",
|
"setnewreminder": "Set a new reminder",
|
||||||
"typeclose": "Close event",
|
"typeclose": "Close event",
|
||||||
"typecourse": "Course 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"><core-format-text [text]="course.fullname"></core-format-text></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"><core-format-text [text]="course.fullname"></core-format-text></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,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, OnInit, OnDestroy, 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 { CoreSyncProvider } from '@providers/sync';
|
||||||
|
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 { AddonCalendarOfflineProvider } from '../../providers/calendar-offline';
|
||||||
|
import { AddonCalendarHelperProvider } from '../../providers/helper';
|
||||||
|
import { AddonCalendarSyncProvider } from '../../providers/calendar-sync';
|
||||||
|
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, OnDestroy {
|
||||||
|
|
||||||
|
@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;
|
||||||
|
protected isDestroyed = false;
|
||||||
|
|
||||||
|
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 calendarOffline: AddonCalendarOfflineProvider,
|
||||||
|
private calendarHelper: AddonCalendarHelperProvider,
|
||||||
|
private calendarSync: AddonCalendarSyncProvider,
|
||||||
|
private fb: FormBuilder,
|
||||||
|
private syncProvider: CoreSyncProvider,
|
||||||
|
@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('');
|
||||||
|
|
||||||
|
const currentDate = this.timeUtils.toDatetimeFormat();
|
||||||
|
|
||||||
|
this.eventForm.addControl('name', this.fb.control('', Validators.required));
|
||||||
|
this.eventForm.addControl('timestart', this.fb.control(currentDate, 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(currentDate));
|
||||||
|
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.
|
||||||
|
*
|
||||||
|
* @param {boolean} [refresh] Whether it's refreshing data.
|
||||||
|
* @return {Promise<any>} Promise resolved when done.
|
||||||
|
*/
|
||||||
|
protected fetchData(refresh?: boolean): Promise<any> {
|
||||||
|
let accessInfo;
|
||||||
|
|
||||||
|
// Get access info.
|
||||||
|
return this.calendarProvider.getAccessInformation(this.courseId).then((info) => {
|
||||||
|
accessInfo = info;
|
||||||
|
|
||||||
|
return this.calendarProvider.getAllowedEventTypes(this.courseId);
|
||||||
|
}).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 (this.eventId && !refresh) {
|
||||||
|
// If editing an event, get offline data. Wait for sync first.
|
||||||
|
|
||||||
|
promises.push(this.calendarSync.waitForSync(AddonCalendarSyncProvider.SYNC_ID).then(() => {
|
||||||
|
// Do not block if the scope is already destroyed.
|
||||||
|
if (!this.isDestroyed) {
|
||||||
|
this.syncProvider.blockOperation(AddonCalendarProvider.COMPONENT, this.eventId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the event data if there's any.
|
||||||
|
return this.calendarOffline.getEvent(this.eventId).then((event) => {
|
||||||
|
this.hasOffline = true;
|
||||||
|
|
||||||
|
// Load the data in the form.
|
||||||
|
this.eventForm.controls.name.setValue(event.name);
|
||||||
|
this.eventForm.controls.timestart.setValue(this.timeUtils.toDatetimeFormat(event.timestart * 1000));
|
||||||
|
this.eventForm.controls.eventtype.setValue(event.eventtype);
|
||||||
|
this.eventForm.controls.categoryid.setValue(event.categoryid || '');
|
||||||
|
this.eventForm.controls.courseid.setValue(event.courseid || '');
|
||||||
|
this.eventForm.controls.groupcourseid.setValue(event.groupcourseid || '');
|
||||||
|
this.eventForm.controls.groupid.setValue(event.groupid || '');
|
||||||
|
this.eventForm.controls.description.setValue(event.description);
|
||||||
|
this.eventForm.controls.location.setValue(event.location);
|
||||||
|
this.eventForm.controls.duration.setValue(event.duration);
|
||||||
|
this.eventForm.controls.timedurationuntil.setValue(
|
||||||
|
this.timeUtils.toDatetimeFormat((event.timedurationuntil * 1000) || Date.now()));
|
||||||
|
this.eventForm.controls.timedurationminutes.setValue(event.timedurationminutes || '');
|
||||||
|
this.eventForm.controls.repeat.setValue(!!event.repeat);
|
||||||
|
this.eventForm.controls.repeats.setValue(event.repeats || '1');
|
||||||
|
}).catch(() => {
|
||||||
|
// No offline data.
|
||||||
|
this.hasOffline = false;
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
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(() => {
|
||||||
|
if (!this.eventTypeControl.value) {
|
||||||
|
// Initialize event type value. 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(true).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 = this.timeUtils.datetimeToDate(formData.timestart),
|
||||||
|
timeUntilDate = this.timeUtils.datetimeToDate(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((result) => {
|
||||||
|
this.returnToList(result.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 {
|
||||||
|
// Unblock the sync because the view will be destroyed and the sync process could be triggered before ngOnDestroy.
|
||||||
|
this.unblockSync();
|
||||||
|
|
||||||
|
if (event) {
|
||||||
|
const data: any = {
|
||||||
|
event: event
|
||||||
|
};
|
||||||
|
this.eventsProvider.trigger(AddonCalendarProvider.NEW_EVENT_EVENT, data, this.currentSite.getId());
|
||||||
|
} else {
|
||||||
|
this.eventsProvider.trigger(AddonCalendarProvider.NEW_EVENT_DISCARDED_EVENT, {}, 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(() => {
|
||||||
|
this.calendarOffline.deleteEvent(this.eventId).then(() => {
|
||||||
|
this.returnToList();
|
||||||
|
}).catch(() => {
|
||||||
|
// Shouldn't happen.
|
||||||
|
this.domUtils.showErrorModal('Error discarding event.');
|
||||||
|
});
|
||||||
|
}).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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected unblockSync(): void {
|
||||||
|
if (this.eventId) {
|
||||||
|
this.syncProvider.unblockOperation(AddonCalendarProvider.COMPONENT, this.eventId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Page destroyed.
|
||||||
|
*/
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.unblockSync();
|
||||||
|
this.isDestroyed = true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -26,6 +26,10 @@
|
||||||
<h2>{{ 'core.course' | translate}}</h2>
|
<h2>{{ 'core.course' | translate}}</h2>
|
||||||
<p><core-format-text [text]="courseName"></core-format-text></p>
|
<p><core-format-text [text]="courseName"></core-format-text></p>
|
||||||
</a>
|
</a>
|
||||||
|
<a ion-item text-wrap *ngIf="groupName">
|
||||||
|
<h2>{{ 'core.group' | translate}}</h2>
|
||||||
|
<p>{{ groupName }}</p>
|
||||||
|
</a>
|
||||||
<a ion-item text-wrap *ngIf="categoryPath">
|
<a ion-item text-wrap *ngIf="categoryPath">
|
||||||
<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>
|
||||||
|
|
|
@ -24,6 +24,7 @@ import { CoreSitesProvider } from '@providers/sites';
|
||||||
import { CoreLocalNotificationsProvider } from '@providers/local-notifications';
|
import { CoreLocalNotificationsProvider } from '@providers/local-notifications';
|
||||||
import { CoreCourseProvider } from '@core/course/providers/course';
|
import { CoreCourseProvider } from '@core/course/providers/course';
|
||||||
import { CoreTimeUtilsProvider } from '@providers/utils/time';
|
import { CoreTimeUtilsProvider } from '@providers/utils/time';
|
||||||
|
import { CoreGroupsProvider } from '@providers/groups';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Page that displays a single calendar event.
|
* Page that displays a single calendar event.
|
||||||
|
@ -46,6 +47,7 @@ export class AddonCalendarEventPage {
|
||||||
event: any = {};
|
event: any = {};
|
||||||
title: string;
|
title: string;
|
||||||
courseName: string;
|
courseName: string;
|
||||||
|
groupName: string;
|
||||||
courseUrl = '';
|
courseUrl = '';
|
||||||
notificationsEnabled = false;
|
notificationsEnabled = false;
|
||||||
moduleUrl = '';
|
moduleUrl = '';
|
||||||
|
@ -58,7 +60,8 @@ export class AddonCalendarEventPage {
|
||||||
private domUtils: CoreDomUtilsProvider, private coursesProvider: CoreCoursesProvider,
|
private domUtils: CoreDomUtilsProvider, private coursesProvider: CoreCoursesProvider,
|
||||||
private calendarHelper: AddonCalendarHelperProvider, private sitesProvider: CoreSitesProvider,
|
private calendarHelper: AddonCalendarHelperProvider, private sitesProvider: CoreSitesProvider,
|
||||||
localNotificationsProvider: CoreLocalNotificationsProvider, private courseProvider: CoreCourseProvider,
|
localNotificationsProvider: CoreLocalNotificationsProvider, private courseProvider: CoreCourseProvider,
|
||||||
private textUtils: CoreTextUtilsProvider, private timeUtils: CoreTimeUtilsProvider) {
|
private textUtils: CoreTextUtilsProvider, private timeUtils: CoreTimeUtilsProvider,
|
||||||
|
private groupsProvider: CoreGroupsProvider) {
|
||||||
|
|
||||||
this.eventId = navParams.get('id');
|
this.eventId = navParams.get('id');
|
||||||
this.notificationsEnabled = localNotificationsProvider.isAvailable();
|
this.notificationsEnabled = localNotificationsProvider.isAvailable();
|
||||||
|
@ -165,6 +168,21 @@ export class AddonCalendarEventPage {
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If it's a group event, get the name of the group.
|
||||||
|
const courseId = canGetById && event.course ? event.course.id : event.courseid;
|
||||||
|
if (courseId && event.groupid) {
|
||||||
|
promises.push(this.groupsProvider.getUserGroupsInCourse(event.courseid).then((groups) => {
|
||||||
|
const group = groups.find((group) => {
|
||||||
|
return group.id == event.groupid;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.groupName = group ? group.name : '';
|
||||||
|
}).catch(() => {
|
||||||
|
// Error getting groups, just don't show the group name.
|
||||||
|
this.groupName = '';
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
if (canGetById && event.iscategoryevent) {
|
if (canGetById && event.iscategoryevent) {
|
||||||
this.categoryPath = event.category.nestedname;
|
this.categoryPath = event.category.nestedname;
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,19 +7,39 @@
|
||||||
</button>
|
</button>
|
||||||
<core-context-menu>
|
<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]="!notificationsEnabled" [priority]="600" [content]="'core.settings.settings' | translate" (action)="openSettings()" [iconAction]="'cog'"></core-context-menu-item>
|
||||||
|
<core-context-menu-item *ngIf="eventsLoaded && 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>
|
</core-context-menu>
|
||||||
</ion-buttons>
|
</ion-buttons>
|
||||||
</ion-navbar>
|
</ion-navbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
<core-split-view>
|
<core-split-view>
|
||||||
<ion-content>
|
<ion-content>
|
||||||
<ion-refresher [enabled]="eventsLoaded" (ionRefresh)="refreshEvents($event)">
|
<ion-refresher [enabled]="eventsLoaded" (ionRefresh)="doRefresh($event)">
|
||||||
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||||
</ion-refresher>
|
</ion-refresher>
|
||||||
<core-loading [hideUntil]="eventsLoaded">
|
<core-loading [hideUntil]="eventsLoaded">
|
||||||
|
<!-- 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>
|
||||||
|
|
||||||
<core-empty-box *ngIf="!filteredEvents || !filteredEvents.length" icon="calendar" [message]="'addon.calendar.noevents' | translate">
|
<core-empty-box *ngIf="!filteredEvents || !filteredEvents.length" icon="calendar" [message]="'addon.calendar.noevents' | translate">
|
||||||
</core-empty-box>
|
</core-empty-box>
|
||||||
|
|
||||||
|
<ion-list *ngIf="offlineEvents && offlineEvents.length" no-margin>
|
||||||
|
<ng-container *ngFor="let event of offlineEvents">
|
||||||
|
<ion-item-divider> {{ 'core.notsent' | translate }}</ion-item-divider>
|
||||||
|
<a ion-item text-wrap [title]="event.name" (click)="gotoEvent(event.id)" [class.core-split-item-selected]="event.id == eventId">
|
||||||
|
<core-icon *ngIf="event.icon" [name]="event.icon" item-start></core-icon>
|
||||||
|
<h2><core-format-text [text]="event.name"></core-format-text></h2>
|
||||||
|
<p>
|
||||||
|
{{ event.timestart * 1000 | coreFormatDate: "strftimedatetimeshort" }}
|
||||||
|
<span *ngIf="event.timeduration"> - {{ (event.timestart + event.timeduration) * 1000 | coreFormatDate: "strftimedatetimeshort" }}</span>
|
||||||
|
</p>
|
||||||
|
</a>
|
||||||
|
</ng-container>
|
||||||
|
</ion-list>
|
||||||
|
|
||||||
<ion-list *ngIf="filteredEvents && filteredEvents.length" no-margin>
|
<ion-list *ngIf="filteredEvents && filteredEvents.length" no-margin>
|
||||||
<ng-container *ngFor="let event of filteredEvents">
|
<ng-container *ngFor="let event of filteredEvents">
|
||||||
<ion-item-divider *ngIf="event.showDate">
|
<ion-item-divider *ngIf="event.showDate">
|
||||||
|
@ -40,5 +60,12 @@
|
||||||
|
|
||||||
<core-infinite-loading [enabled]="canLoadMore" (action)="loadMoreEvents($event)" [error]="loadMoreError"></core-infinite-loading>
|
<core-infinite-loading [enabled]="canLoadMore" (action)="loadMoreEvents($event)" [error]="loadMoreError"></core-infinite-loading>
|
||||||
</core-loading>
|
</core-loading>
|
||||||
|
|
||||||
|
<!-- 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>
|
</ion-content>
|
||||||
</core-split-view>
|
</core-split-view>
|
|
@ -12,11 +12,13 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { Component, ViewChild, OnDestroy } from '@angular/core';
|
import { Component, ViewChild, OnDestroy, NgZone } from '@angular/core';
|
||||||
import { IonicPage, Content, PopoverController, NavParams, NavController } from 'ionic-angular';
|
import { IonicPage, Content, PopoverController, NavParams, NavController } from 'ionic-angular';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { AddonCalendarProvider } from '../../providers/calendar';
|
import { AddonCalendarProvider } from '../../providers/calendar';
|
||||||
|
import { AddonCalendarOfflineProvider } from '../../providers/calendar-offline';
|
||||||
import { AddonCalendarHelperProvider } from '../../providers/helper';
|
import { AddonCalendarHelperProvider } from '../../providers/helper';
|
||||||
|
import { AddonCalendarSyncProvider } from '../../providers/calendar-sync';
|
||||||
import { CoreCoursesProvider } from '@core/courses/providers/courses';
|
import { CoreCoursesProvider } from '@core/courses/providers/courses';
|
||||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||||
import { CoreUtilsProvider } from '@providers/utils/utils';
|
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||||
|
@ -27,6 +29,7 @@ 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';
|
||||||
import * as moment from 'moment';
|
import * as moment from 'moment';
|
||||||
|
import { Network } from '@ionic-native/network';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Page that displays the list of calendar events.
|
* Page that displays the list of calendar events.
|
||||||
|
@ -54,10 +57,15 @@ export class AddonCalendarListPage implements OnDestroy {
|
||||||
protected obsDefaultTimeChange: any;
|
protected obsDefaultTimeChange: any;
|
||||||
protected eventId: number;
|
protected eventId: number;
|
||||||
protected preSelectedCourseId: number;
|
protected preSelectedCourseId: number;
|
||||||
|
protected newEventObserver: any;
|
||||||
|
protected discardedObserver: any;
|
||||||
|
protected syncObserver: any;
|
||||||
|
protected onlineObserver: any;
|
||||||
|
|
||||||
courses: any[];
|
courses: any[];
|
||||||
eventsLoaded = false;
|
eventsLoaded = false;
|
||||||
events = [];
|
events = [];
|
||||||
|
offlineEvents = [];
|
||||||
notificationsEnabled = false;
|
notificationsEnabled = false;
|
||||||
filteredEvents = [];
|
filteredEvents = [];
|
||||||
canLoadMore = false;
|
canLoadMore = false;
|
||||||
|
@ -65,15 +73,22 @@ export class AddonCalendarListPage implements OnDestroy {
|
||||||
filter = {
|
filter = {
|
||||||
course: this.allCourses
|
course: this.allCourses
|
||||||
};
|
};
|
||||||
|
canCreate = false;
|
||||||
|
hasOffline = false;
|
||||||
|
isOnline = false;
|
||||||
|
syncIcon: string; // Sync icon.
|
||||||
|
|
||||||
constructor(private translate: TranslateService, private calendarProvider: AddonCalendarProvider, navParams: NavParams,
|
constructor(private translate: TranslateService, 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,
|
private calendarHelper: AddonCalendarHelperProvider, private sitesProvider: CoreSitesProvider, zone: NgZone,
|
||||||
localNotificationsProvider: CoreLocalNotificationsProvider, private popoverCtrl: PopoverController,
|
localNotificationsProvider: CoreLocalNotificationsProvider, private popoverCtrl: PopoverController,
|
||||||
eventsProvider: CoreEventsProvider, private navCtrl: NavController, appProvider: CoreAppProvider) {
|
private eventsProvider: CoreEventsProvider, private navCtrl: NavController, private appProvider: CoreAppProvider,
|
||||||
|
private calendarOffline: AddonCalendarOfflineProvider, private calendarSync: AddonCalendarSyncProvider,
|
||||||
|
network: Network) {
|
||||||
|
|
||||||
this.siteHomeId = sitesProvider.getCurrentSite().getSiteHomeId();
|
this.siteHomeId = sitesProvider.getCurrentSite().getSiteHomeId();
|
||||||
this.notificationsEnabled = localNotificationsProvider.isAvailable();
|
this.notificationsEnabled = localNotificationsProvider.isAvailable();
|
||||||
|
|
||||||
if (this.notificationsEnabled) {
|
if (this.notificationsEnabled) {
|
||||||
// Re-schedule events if default time changes.
|
// Re-schedule events if default time changes.
|
||||||
this.obsDefaultTimeChange = eventsProvider.on(AddonCalendarProvider.DEFAULT_NOTIFICATION_TIME_CHANGED, () => {
|
this.obsDefaultTimeChange = eventsProvider.on(AddonCalendarProvider.DEFAULT_NOTIFICATION_TIME_CHANGED, () => {
|
||||||
|
@ -83,6 +98,50 @@ export class AddonCalendarListPage implements OnDestroy {
|
||||||
|
|
||||||
this.eventId = navParams.get('eventId') || false;
|
this.eventId = navParams.get('eventId') || false;
|
||||||
this.preSelectedCourseId = navParams.get('courseId') || null;
|
this.preSelectedCourseId = navParams.get('courseId') || null;
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
if (this.splitviewCtrl.isOn()) {
|
||||||
|
// Discussion added, clear details page.
|
||||||
|
this.splitviewCtrl.emptyDetails();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.eventsLoaded = false;
|
||||||
|
this.refreshEvents(true, false).finally(() => {
|
||||||
|
|
||||||
|
// In tablet mode try to open the event (only if it's an online event).
|
||||||
|
if (this.splitviewCtrl.isOn() && data.event.id > 0) {
|
||||||
|
this.gotoEvent(data.event.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, sitesProvider.getCurrentSiteId());
|
||||||
|
|
||||||
|
// Listen for new event discarded event. When it does, reload the data.
|
||||||
|
this.discardedObserver = eventsProvider.on(AddonCalendarProvider.NEW_EVENT_DISCARDED_EVENT, () => {
|
||||||
|
if (this.splitviewCtrl.isOn()) {
|
||||||
|
// Discussion added, clear details page.
|
||||||
|
this.splitviewCtrl.emptyDetails();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.eventsLoaded = false;
|
||||||
|
this.refreshEvents(true, false);
|
||||||
|
}, sitesProvider.getCurrentSiteId());
|
||||||
|
|
||||||
|
// Refresh data if calendar events are synchronized automatically.
|
||||||
|
this.syncObserver = eventsProvider.on(AddonCalendarSyncProvider.AUTO_SYNCED, (data) => {
|
||||||
|
this.eventsLoaded = false;
|
||||||
|
this.refreshEvents();
|
||||||
|
}, sitesProvider.getCurrentSiteId());
|
||||||
|
|
||||||
|
// Refresh online status when changes.
|
||||||
|
this.onlineObserver = network.onchange().subscribe((online) => {
|
||||||
|
// Execute the callback in the Angular zone, so change detection doesn't stop working.
|
||||||
|
zone.run(() => {
|
||||||
|
this.isOnline = online;
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -94,13 +153,13 @@ export class AddonCalendarListPage implements OnDestroy {
|
||||||
this.gotoEvent(this.eventId);
|
this.gotoEvent(this.eventId);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.fetchData().then(() => {
|
this.syncIcon = 'spinner';
|
||||||
|
|
||||||
|
this.fetchData(false, true, false).then(() => {
|
||||||
if (!this.eventId && this.splitviewCtrl.isOn() && this.events.length > 0) {
|
if (!this.eventId && this.splitviewCtrl.isOn() && this.events.length > 0) {
|
||||||
// Take first and load it.
|
// Take first and load it.
|
||||||
this.gotoEvent(this.events[0].id);
|
this.gotoEvent(this.events[0].id);
|
||||||
}
|
}
|
||||||
}).finally(() => {
|
|
||||||
this.eventsLoaded = true;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,25 +167,76 @@ export class AddonCalendarListPage implements OnDestroy {
|
||||||
* Fetch all the data required for the view.
|
* Fetch all the data required for the view.
|
||||||
*
|
*
|
||||||
* @param {boolean} [refresh] Empty events array first.
|
* @param {boolean} [refresh] Empty events array first.
|
||||||
|
* @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.
|
* @return {Promise<any>} Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
fetchData(refresh: boolean = false): Promise<any> {
|
fetchData(refresh?: boolean, sync?: boolean, showErrors?: boolean): Promise<any> {
|
||||||
this.daysLoaded = 0;
|
this.daysLoaded = 0;
|
||||||
this.emptyEventsTimes = 0;
|
this.emptyEventsTimes = 0;
|
||||||
|
this.isOnline = this.appProvider.isOnline();
|
||||||
|
|
||||||
// Load courses for the popover.
|
let promise;
|
||||||
return this.coursesProvider.getUserCourses(false).then((courses) => {
|
|
||||||
// Add "All courses".
|
|
||||||
courses.unshift(this.allCourses);
|
|
||||||
this.courses = courses;
|
|
||||||
|
|
||||||
if (this.preSelectedCourseId) {
|
if (sync) {
|
||||||
this.filter.course = courses.find((course) => {
|
// Try to synchronize offline events.
|
||||||
return course.id == this.preSelectedCourseId;
|
promise = this.calendarSync.syncEvents().then((result) => {
|
||||||
});
|
if (result.warnings && result.warnings.length) {
|
||||||
}
|
this.domUtils.showErrorModal(result.warnings[0]);
|
||||||
|
}
|
||||||
|
|
||||||
return this.fetchEvents(refresh);
|
if (result.updated) {
|
||||||
|
// Trigger a manual sync event.
|
||||||
|
this.eventsProvider.trigger(AddonCalendarSyncProvider.MANUAL_SYNCED, {
|
||||||
|
source: 'list'
|
||||||
|
}, this.sitesProvider.getCurrentSiteId());
|
||||||
|
}
|
||||||
|
}).catch((error) => {
|
||||||
|
if (showErrors) {
|
||||||
|
this.domUtils.showErrorModalDefault(error, 'core.errorsync', true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
promise = Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
return promise.then(() => {
|
||||||
|
|
||||||
|
const promises = [];
|
||||||
|
const courseId = this.filter.course.id != this.allCourses.id ? this.filter.course.id : undefined;
|
||||||
|
|
||||||
|
promises.push(this.calendarHelper.canEditEvents(courseId).then((canEdit) => {
|
||||||
|
this.canCreate = canEdit;
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 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.preSelectedCourseId) {
|
||||||
|
this.filter.course = courses.find((course) => {
|
||||||
|
return course.id == this.preSelectedCourseId;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.fetchEvents(refresh);
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Get offline events.
|
||||||
|
promises.push(this.calendarOffline.getAllEvents().then((events) => {
|
||||||
|
this.hasOffline = !!events.length;
|
||||||
|
|
||||||
|
// Format data and sort by timestart.
|
||||||
|
events.forEach(this.calendarHelper.formatEventData.bind(this.calendarHelper));
|
||||||
|
this.offlineEvents = events.sort((a, b) => a.timestart - b.timestart);
|
||||||
|
}));
|
||||||
|
|
||||||
|
return Promise.all(promises);
|
||||||
|
}).finally(() => {
|
||||||
|
this.eventsLoaded = true;
|
||||||
|
this.syncIcon = 'sync';
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,7 +246,7 @@ export class AddonCalendarListPage implements OnDestroy {
|
||||||
* @param {boolean} [refresh] Empty events array first.
|
* @param {boolean} [refresh] Empty events array first.
|
||||||
* @return {Promise<any>} Promise resolved when done.
|
* @return {Promise<any>} Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
fetchEvents(refresh: boolean = false): Promise<any> {
|
fetchEvents(refresh?: boolean): Promise<any> {
|
||||||
this.loadMoreError = false;
|
this.loadMoreError = false;
|
||||||
|
|
||||||
return this.calendarProvider.getEventsList(this.daysLoaded, AddonCalendarProvider.DAYS_INTERVAL).then((events) => {
|
return this.calendarProvider.getEventsList(this.daysLoaded, AddonCalendarProvider.DAYS_INTERVAL).then((events) => {
|
||||||
|
@ -154,6 +264,8 @@ export class AddonCalendarListPage implements OnDestroy {
|
||||||
return this.fetchEvents();
|
return this.fetchEvents();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
events.forEach(this.calendarHelper.formatEventData.bind(this.calendarHelper));
|
||||||
|
|
||||||
// Sort the events by timestart, they're ordered by id.
|
// Sort the events by timestart, they're ordered by id.
|
||||||
events.sort((a, b) => {
|
events.sort((a, b) => {
|
||||||
if (a.timestart == b.timestart) {
|
if (a.timestart == b.timestart) {
|
||||||
|
@ -163,7 +275,6 @@ export class AddonCalendarListPage implements OnDestroy {
|
||||||
return a.timestart - b.timestart;
|
return a.timestart - b.timestart;
|
||||||
});
|
});
|
||||||
|
|
||||||
events.forEach(this.calendarHelper.formatEventData.bind(this.calendarHelper));
|
|
||||||
this.getCategories = this.shouldLoadCategories(events);
|
this.getCategories = this.shouldLoadCategories(events);
|
||||||
|
|
||||||
if (refresh) {
|
if (refresh) {
|
||||||
|
@ -305,25 +416,47 @@ export class AddonCalendarListPage implements OnDestroy {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.eventsLoaded) {
|
||||||
|
return this.refreshEvents(true, showErrors).finally(() => {
|
||||||
|
refresher && refresher.complete();
|
||||||
|
done && done();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Refresh the events.
|
* Refresh the events.
|
||||||
*
|
*
|
||||||
* @param {any} refresher Refresher.
|
* @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.
|
||||||
*/
|
*/
|
||||||
refreshEvents(refresher: any): void {
|
refreshEvents(sync?: boolean, showErrors?: boolean): Promise<any> {
|
||||||
|
this.syncIcon = 'spinner';
|
||||||
|
|
||||||
const promises = [];
|
const promises = [];
|
||||||
|
|
||||||
promises.push(this.calendarProvider.invalidateEventsList());
|
promises.push(this.calendarProvider.invalidateEventsList());
|
||||||
|
promises.push(this.calendarProvider.invalidateAllowedEventTypes());
|
||||||
|
|
||||||
if (this.categoriesRetrieved) {
|
if (this.categoriesRetrieved) {
|
||||||
promises.push(this.coursesProvider.invalidateCategories(0, true));
|
promises.push(this.coursesProvider.invalidateCategories(0, true));
|
||||||
this.categoriesRetrieved = false;
|
this.categoriesRetrieved = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Promise.all(promises).finally(() => {
|
return Promise.all(promises).finally(() => {
|
||||||
this.fetchData(true).finally(() => {
|
return this.fetchData(true, sync, showErrors);
|
||||||
refresher.complete();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -377,6 +510,13 @@ export class AddonCalendarListPage implements OnDestroy {
|
||||||
this.domUtils.scrollToTop(this.content);
|
this.domUtils.scrollToTop(this.content);
|
||||||
|
|
||||||
this.filteredEvents = this.getFilteredEvents();
|
this.filteredEvents = this.getFilteredEvents();
|
||||||
|
|
||||||
|
// Course viewed has changed, check if the user can create events for this course calendar.
|
||||||
|
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({
|
popover.present({
|
||||||
|
@ -384,6 +524,24 @@ export class AddonCalendarListPage implements OnDestroy {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.filter.course.id != this.allCourses.id) {
|
||||||
|
params.courseId = this.filter.course.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.splitviewCtrl.push('AddonCalendarEditEventPage', params);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Open calendar events settings.
|
* Open calendar events settings.
|
||||||
*/
|
*/
|
||||||
|
@ -398,7 +556,15 @@ export class AddonCalendarListPage implements OnDestroy {
|
||||||
*/
|
*/
|
||||||
gotoEvent(eventId: number): void {
|
gotoEvent(eventId: number): void {
|
||||||
this.eventId = eventId;
|
this.eventId = eventId;
|
||||||
this.splitviewCtrl.push('AddonCalendarEventPage', { id: eventId });
|
|
||||||
|
if (eventId < 0) {
|
||||||
|
// It's an offline event, go to the edit page.
|
||||||
|
this.openEdit(eventId);
|
||||||
|
} else {
|
||||||
|
this.splitviewCtrl.push('AddonCalendarEventPage', {
|
||||||
|
id: eventId
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -406,5 +572,9 @@ export class AddonCalendarListPage implements OnDestroy {
|
||||||
*/
|
*/
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
this.obsDefaultTimeChange && this.obsDefaultTimeChange.off();
|
this.obsDefaultTimeChange && this.obsDefaultTimeChange.off();
|
||||||
|
this.newEventObserver && this.newEventObserver.off();
|
||||||
|
this.discardedObserver && this.discardedObserver.off();
|
||||||
|
this.syncObserver && this.syncObserver.off();
|
||||||
|
this.onlineObserver && this.onlineObserver.off();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,214 @@
|
||||||
|
// (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 { CoreSitesProvider, CoreSiteSchema } from '@providers/sites';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service to handle offline calendar events.
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class AddonCalendarOfflineProvider {
|
||||||
|
|
||||||
|
// Variables for database.
|
||||||
|
static EVENTS_TABLE = 'addon_calendar_offline_events';
|
||||||
|
|
||||||
|
protected siteSchema: CoreSiteSchema = {
|
||||||
|
name: 'AddonCalendarOfflineProvider',
|
||||||
|
version: 1,
|
||||||
|
tables: [
|
||||||
|
{
|
||||||
|
name: AddonCalendarOfflineProvider.EVENTS_TABLE,
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'id', // Negative for offline entries.
|
||||||
|
type: 'INTEGER',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'name',
|
||||||
|
type: 'TEXT',
|
||||||
|
notNull: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'timestart',
|
||||||
|
type: 'INTEGER',
|
||||||
|
notNull: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'eventtype',
|
||||||
|
type: 'TEXT',
|
||||||
|
notNull: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'categoryid',
|
||||||
|
type: 'INTEGER',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'courseid',
|
||||||
|
type: 'INTEGER',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'groupcourseid',
|
||||||
|
type: 'INTEGER',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'groupid',
|
||||||
|
type: 'INTEGER',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'description',
|
||||||
|
type: 'TEXT',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'location',
|
||||||
|
type: 'TEXT',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'duration',
|
||||||
|
type: 'INTEGER',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'timedurationuntil',
|
||||||
|
type: 'INTEGER',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'timedurationminutes',
|
||||||
|
type: 'INTEGER',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'repeat',
|
||||||
|
type: 'INTEGER',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'repeats',
|
||||||
|
type: 'TEXT',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'userid',
|
||||||
|
type: 'INTEGER',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'timecreated',
|
||||||
|
type: 'INTEGER',
|
||||||
|
}
|
||||||
|
],
|
||||||
|
primaryKeys: ['id']
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(private sitesProvider: CoreSitesProvider) {
|
||||||
|
this.sitesProvider.registerSiteSchema(this.siteSchema);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete an offline event.
|
||||||
|
*
|
||||||
|
* @param {number} eventId Event ID.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<any>} Promise resolved if deleted, rejected if failure.
|
||||||
|
*/
|
||||||
|
deleteEvent(eventId: number, siteId?: string): Promise<any> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
const conditions: any = {
|
||||||
|
id: eventId
|
||||||
|
};
|
||||||
|
|
||||||
|
return site.getDb().deleteRecords(AddonCalendarOfflineProvider.EVENTS_TABLE, conditions);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all offline events.
|
||||||
|
*
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<any[]>} Promise resolved with events.
|
||||||
|
*/
|
||||||
|
getAllEvents(siteId?: string): Promise<any[]> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
return site.getDb().getRecords(AddonCalendarOfflineProvider.EVENTS_TABLE);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an offline event.
|
||||||
|
*
|
||||||
|
* @param {number} eventId Event ID.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<any>} Promise resolved with the event.
|
||||||
|
*/
|
||||||
|
getEvent(eventId: number, siteId?: string): Promise<any> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
const conditions: any = {
|
||||||
|
id: eventId
|
||||||
|
};
|
||||||
|
|
||||||
|
return site.getDb().getRecord(AddonCalendarOfflineProvider.EVENTS_TABLE, conditions);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if there are offline events to send.
|
||||||
|
*
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<boolean>} Promise resolved with boolean: true if has offline events, false otherwise.
|
||||||
|
*/
|
||||||
|
hasEvents(siteId?: string): Promise<boolean> {
|
||||||
|
return this.getAllEvents(siteId).then((events) => {
|
||||||
|
return !!events.length;
|
||||||
|
}).catch(() => {
|
||||||
|
// No offline data found, return false.
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Offline version for adding a new discussion to a forum.
|
||||||
|
*
|
||||||
|
* @param {number} eventId Event ID. If it's a new event, set it to undefined/null.
|
||||||
|
* @param {any} data Event data.
|
||||||
|
* @param {number} [timeCreated] The time the event was created. If not defined, current time.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<any>} Promise resolved with the stored event.
|
||||||
|
*/
|
||||||
|
saveEvent(eventId: number, data: any, timeCreated?: number, siteId?: string): Promise<any> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
timeCreated = timeCreated || Date.now();
|
||||||
|
|
||||||
|
const event = {
|
||||||
|
id: eventId || -timeCreated,
|
||||||
|
name: data.name,
|
||||||
|
timestart: data.timestart,
|
||||||
|
eventtype: data.eventtype,
|
||||||
|
categoryid: data.categoryid || null,
|
||||||
|
courseid: data.courseid || null,
|
||||||
|
groupcourseid: data.groupcourseid || null,
|
||||||
|
groupid: data.groupid || null,
|
||||||
|
description: data.description && data.description.text,
|
||||||
|
location: data.location,
|
||||||
|
duration: data.duration,
|
||||||
|
timedurationuntil: data.timedurationuntil,
|
||||||
|
timedurationminutes: data.timedurationminutes,
|
||||||
|
repeat: data.repeat ? 1 : 0,
|
||||||
|
repeats: data.repeats,
|
||||||
|
timecreated: timeCreated,
|
||||||
|
userid: site.getUserId()
|
||||||
|
};
|
||||||
|
|
||||||
|
return site.getDb().insertRecord(AddonCalendarOfflineProvider.EVENTS_TABLE, event).then(() => {
|
||||||
|
return event;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,230 @@
|
||||||
|
// (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 { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { CoreSyncBaseProvider } from '@classes/base-sync';
|
||||||
|
import { CoreCourseProvider } from '@core/course/providers/course';
|
||||||
|
import { CoreAppProvider } from '@providers/app';
|
||||||
|
import { CoreLoggerProvider } from '@providers/logger';
|
||||||
|
import { CoreEventsProvider } from '@providers/events';
|
||||||
|
import { CoreSitesProvider } from '@providers/sites';
|
||||||
|
import { CoreSyncProvider } from '@providers/sync';
|
||||||
|
import { CoreTextUtilsProvider } from '@providers/utils/text';
|
||||||
|
import { CoreTimeUtilsProvider } from '@providers/utils/time';
|
||||||
|
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||||
|
import { AddonCalendarProvider } from './calendar';
|
||||||
|
import { AddonCalendarOfflineProvider } from './calendar-offline';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service to sync calendar.
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class AddonCalendarSyncProvider extends CoreSyncBaseProvider {
|
||||||
|
|
||||||
|
static AUTO_SYNCED = 'addon_calendar_autom_synced';
|
||||||
|
static MANUAL_SYNCED = 'addon_calendar_manual_synced';
|
||||||
|
static SYNC_ID = 'calendar';
|
||||||
|
|
||||||
|
protected componentTranslate: string;
|
||||||
|
|
||||||
|
constructor(translate: TranslateService,
|
||||||
|
appProvider: CoreAppProvider,
|
||||||
|
courseProvider: CoreCourseProvider,
|
||||||
|
private eventsProvider: CoreEventsProvider,
|
||||||
|
loggerProvider: CoreLoggerProvider,
|
||||||
|
sitesProvider: CoreSitesProvider,
|
||||||
|
syncProvider: CoreSyncProvider,
|
||||||
|
textUtils: CoreTextUtilsProvider,
|
||||||
|
timeUtils: CoreTimeUtilsProvider,
|
||||||
|
private utils: CoreUtilsProvider,
|
||||||
|
private calendarProvider: AddonCalendarProvider,
|
||||||
|
private calendarOffline: AddonCalendarOfflineProvider) {
|
||||||
|
|
||||||
|
super('AddonCalendarSyncProvider', loggerProvider, sitesProvider, appProvider, syncProvider, textUtils, translate,
|
||||||
|
timeUtils);
|
||||||
|
|
||||||
|
this.componentTranslate = this.translate.instant('addon.calendar.calendarevent');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to synchronize all events in a certain site or in all sites.
|
||||||
|
*
|
||||||
|
* @param {string} [siteId] Site ID to sync. If not defined, sync all sites.
|
||||||
|
* @param {boolean} [force] Wether to force sync not depending on last execution.
|
||||||
|
* @return {Promise<any>} Promise resolved if sync is successful, rejected if sync fails.
|
||||||
|
*/
|
||||||
|
syncAllEvents(siteId?: string, force?: boolean): Promise<any> {
|
||||||
|
return this.syncOnSites('all calendars', this.syncAllEventsFunc.bind(this), [force], siteId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sync all events on a site.
|
||||||
|
*
|
||||||
|
* @param {string} siteId Site ID to sync.
|
||||||
|
* @param {boolean} [force] Wether to force sync not depending on last execution.
|
||||||
|
* @return {Promise<any>} Promise resolved if sync is successful, rejected if sync fails.
|
||||||
|
*/
|
||||||
|
protected syncAllEventsFunc(siteId: string, force?: boolean): Promise<any> {
|
||||||
|
const promise = force ? this.syncEvents(siteId) : this.syncEventsIfNeeded(siteId);
|
||||||
|
|
||||||
|
return promise.then((result) => {
|
||||||
|
if (result && result.updated) {
|
||||||
|
// Sync successful, send event.
|
||||||
|
this.eventsProvider.trigger(AddonCalendarSyncProvider.AUTO_SYNCED, {
|
||||||
|
warnings: result.warnings,
|
||||||
|
events: result.events
|
||||||
|
}, siteId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sync a site events only if a certain time has passed since the last time.
|
||||||
|
*
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<any>} Promise resolved when the events are synced or if it doesn't need to be synced.
|
||||||
|
*/
|
||||||
|
syncEventsIfNeeded(siteId?: string): Promise<any> {
|
||||||
|
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||||
|
|
||||||
|
return this.isSyncNeeded(AddonCalendarSyncProvider.SYNC_ID, siteId).then((needed) => {
|
||||||
|
if (needed) {
|
||||||
|
return this.syncEvents(siteId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Synchronize all offline events of a certain site.
|
||||||
|
*
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<any>} Promise resolved if sync is successful, rejected otherwise.
|
||||||
|
*/
|
||||||
|
syncEvents(siteId?: string): Promise<any> {
|
||||||
|
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||||
|
|
||||||
|
if (this.isSyncing(AddonCalendarSyncProvider.SYNC_ID, siteId)) {
|
||||||
|
// There's already a sync ongoing for this site, return the promise.
|
||||||
|
return this.getOngoingSync(AddonCalendarSyncProvider.SYNC_ID, siteId);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.debug('Try to sync calendar events for site ' + siteId);
|
||||||
|
|
||||||
|
const result = {
|
||||||
|
warnings: [],
|
||||||
|
events: [],
|
||||||
|
updated: false
|
||||||
|
};
|
||||||
|
let offlineEvents;
|
||||||
|
|
||||||
|
// Get offline events.
|
||||||
|
const syncPromise = this.calendarOffline.getAllEvents(siteId).catch(() => {
|
||||||
|
// No offline data found, return empty list.
|
||||||
|
return [];
|
||||||
|
}).then((events) => {
|
||||||
|
offlineEvents = events;
|
||||||
|
|
||||||
|
if (!events.length) {
|
||||||
|
// Nothing to sync.
|
||||||
|
return;
|
||||||
|
} else if (!this.appProvider.isOnline()) {
|
||||||
|
// Cannot sync in offline.
|
||||||
|
return Promise.reject(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
const promises = [];
|
||||||
|
|
||||||
|
events.forEach((event) => {
|
||||||
|
promises.push(this.syncOfflineEvent(event, result, siteId));
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.utils.allPromises(promises);
|
||||||
|
}).then(() => {
|
||||||
|
if (result.updated) {
|
||||||
|
// Data has been sent to server. Now invalidate the WS calls.
|
||||||
|
const promises = [
|
||||||
|
this.calendarProvider.invalidateEventsList(siteId),
|
||||||
|
];
|
||||||
|
|
||||||
|
offlineEvents.forEach((event) => {
|
||||||
|
if (event.id > 0) {
|
||||||
|
// An event was edited, invalidate its data too.
|
||||||
|
promises.push(this.calendarProvider.invalidateEvent(event.id, siteId));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return Promise.all(promises).catch(() => {
|
||||||
|
// Ignore errors.
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).then(() => {
|
||||||
|
// Sync finished, set sync time.
|
||||||
|
return this.setSyncTime(AddonCalendarSyncProvider.SYNC_ID, siteId).catch(() => {
|
||||||
|
// Ignore errors.
|
||||||
|
});
|
||||||
|
}).then(() => {
|
||||||
|
// All done, return the result.
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.addOngoingSync(AddonCalendarSyncProvider.SYNC_ID, syncPromise, siteId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Synchronize an offline event.
|
||||||
|
*
|
||||||
|
* @param {any} event The event to sync.
|
||||||
|
* @param {any} result Object where to store the result of the sync.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<any>} Promise resolved if sync is successful, rejected otherwise.
|
||||||
|
*/
|
||||||
|
protected syncOfflineEvent(event: any, result: any, siteId?: string): Promise<any> {
|
||||||
|
|
||||||
|
// Verify that event isn't blocked.
|
||||||
|
if (this.syncProvider.isBlocked(AddonCalendarProvider.COMPONENT, event.id, siteId)) {
|
||||||
|
this.logger.debug('Cannot sync event ' + event.name + ' because it is blocked.');
|
||||||
|
|
||||||
|
return Promise.reject(this.translate.instant('core.errorsyncblocked', {$a: this.componentTranslate}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to send the data.
|
||||||
|
const data = this.utils.clone(event); // Clone the object because it will be modified in the submit function.
|
||||||
|
|
||||||
|
return this.calendarProvider.submitEventOnline(event.id > 0 ? event.id : undefined, data, siteId).then((newEvent) => {
|
||||||
|
result.updated = true;
|
||||||
|
result.events.push(newEvent);
|
||||||
|
|
||||||
|
// Event sent, delete the offline data.
|
||||||
|
return this.calendarOffline.deleteEvent(event.id, siteId);
|
||||||
|
}).catch((error) => {
|
||||||
|
if (this.utils.isWebServiceError(error)) {
|
||||||
|
// The WebService has thrown an error, this means that the event cannot be created. Delete it.
|
||||||
|
result.updated = true;
|
||||||
|
|
||||||
|
return this.calendarOffline.deleteEvent(event.id, siteId).then(() => {
|
||||||
|
// Event deleted, add a warning.
|
||||||
|
result.warnings.push(this.translate.instant('core.warningofflinedatadeleted', {
|
||||||
|
component: this.componentTranslate,
|
||||||
|
name: event.name,
|
||||||
|
error: this.textUtils.getErrorMessageFromError(error)
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Local error, reject.
|
||||||
|
return Promise.reject(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,13 +17,16 @@ import { CoreLoggerProvider } from '@providers/logger';
|
||||||
import { CoreSitesProvider, CoreSiteSchema } from '@providers/sites';
|
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 { CoreTimeUtilsProvider } from '@providers/utils/time';
|
import { CoreTimeUtilsProvider } from '@providers/utils/time';
|
||||||
|
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';
|
||||||
import { CoreLocalNotificationsProvider } from '@providers/local-notifications';
|
import { CoreLocalNotificationsProvider } from '@providers/local-notifications';
|
||||||
import { CoreConfigProvider } from '@providers/config';
|
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';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service to handle calendar events.
|
* Service to handle calendar events.
|
||||||
|
@ -35,6 +38,13 @@ export class AddonCalendarProvider {
|
||||||
static DEFAULT_NOTIFICATION_TIME_CHANGED = 'AddonCalendarDefaultNotificationTimeChangedEvent';
|
static DEFAULT_NOTIFICATION_TIME_CHANGED = 'AddonCalendarDefaultNotificationTimeChangedEvent';
|
||||||
static DEFAULT_NOTIFICATION_TIME_SETTING = 'mmaCalendarDefaultNotifTime';
|
static DEFAULT_NOTIFICATION_TIME_SETTING = 'mmaCalendarDefaultNotifTime';
|
||||||
static DEFAULT_NOTIFICATION_TIME = 60;
|
static DEFAULT_NOTIFICATION_TIME = 60;
|
||||||
|
static NEW_EVENT_EVENT = 'addon_calendar_new_event';
|
||||||
|
static NEW_EVENT_DISCARDED_EVENT = 'addon_calendar_new_event_discarded';
|
||||||
|
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:';
|
protected ROOT_CACHE_KEY = 'mmaCalendar:';
|
||||||
|
|
||||||
// Variables for database.
|
// Variables for database.
|
||||||
|
@ -206,11 +216,38 @@ export class AddonCalendarProvider {
|
||||||
|
|
||||||
constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private groupsProvider: CoreGroupsProvider,
|
constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private groupsProvider: CoreGroupsProvider,
|
||||||
private coursesProvider: CoreCoursesProvider, private timeUtils: CoreTimeUtilsProvider,
|
private coursesProvider: CoreCoursesProvider, private timeUtils: CoreTimeUtilsProvider,
|
||||||
private localNotificationsProvider: CoreLocalNotificationsProvider, private configProvider: CoreConfigProvider) {
|
private localNotificationsProvider: CoreLocalNotificationsProvider, private configProvider: CoreConfigProvider,
|
||||||
|
private utils: CoreUtilsProvider, private calendarOffline: AddonCalendarOfflineProvider,
|
||||||
|
private appProvider: CoreAppProvider) {
|
||||||
this.logger = logger.getInstance('AddonCalendarProvider');
|
this.logger = logger.getInstance('AddonCalendarProvider');
|
||||||
this.sitesProvider.registerSiteSchema(this.siteSchema);
|
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.
|
* Removes expired events from local DB.
|
||||||
*
|
*
|
||||||
|
@ -255,6 +292,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.
|
* Get all calendar events from local Db.
|
||||||
*
|
*
|
||||||
|
@ -267,6 +337,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.
|
* Get the configured default notification time.
|
||||||
*
|
*
|
||||||
|
@ -504,6 +618,32 @@ export class AddonCalendarProvider {
|
||||||
return this.getEventsListPrefixCacheKey() + daysToStart + ':' + daysInterval;
|
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.
|
* Invalidates events list and all the single events and related info.
|
||||||
*
|
*
|
||||||
|
@ -780,4 +920,79 @@ export class AddonCalendarProvider {
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submit a calendar event.
|
||||||
|
*
|
||||||
|
* @param {number} eventId ID of the event. If undefined/null, create a new event.
|
||||||
|
* @param {any} formData Form data.
|
||||||
|
* @param {number} [timeCreated] The time the event was created. Only if modifying a new offline event.
|
||||||
|
* @param {boolean} [forceOffline] True to always save it in offline.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<{sent: boolean, event: any}>} Promise resolved with the event and a boolean indicating if data was
|
||||||
|
* sent to server or stored in offline.
|
||||||
|
*/
|
||||||
|
submitEvent(eventId: number, formData: any, timeCreated?: number, forceOffline?: boolean, siteId?: string):
|
||||||
|
Promise<{sent: boolean, event: any}> {
|
||||||
|
|
||||||
|
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||||
|
|
||||||
|
// Function to store the event to be synchronized later.
|
||||||
|
const storeOffline = (): Promise<{sent: boolean, event: any}> => {
|
||||||
|
return this.calendarOffline.saveEvent(eventId, formData, timeCreated, siteId).then((event) => {
|
||||||
|
return {sent: false, event: event};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
if (forceOffline || !this.appProvider.isOnline()) {
|
||||||
|
// App is offline, store the event.
|
||||||
|
return storeOffline();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the event is already stored, discard it first.
|
||||||
|
return this.calendarOffline.deleteEvent(eventId, siteId).then(() => {
|
||||||
|
return this.submitEventOnline(eventId, formData, siteId).then((event) => {
|
||||||
|
return {sent: true, event: event};
|
||||||
|
}).catch((error) => {
|
||||||
|
if (error && !this.utils.isWebServiceError(error)) {
|
||||||
|
// Couldn't connect to server, store in offline.
|
||||||
|
return storeOffline();
|
||||||
|
} else {
|
||||||
|
// The WebService has thrown an error, reject.
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submit an event, either to create it or to edit it. It will fail if offline or cannot connect.
|
||||||
|
*
|
||||||
|
* @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.
|
||||||
|
*/
|
||||||
|
submitEventOnline(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(this.utils.createFakeWSError(''));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.event;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,8 @@
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { CoreLoggerProvider } from '@providers/logger';
|
import { CoreLoggerProvider } from '@providers/logger';
|
||||||
import { CoreCourseProvider } from '@core/course/providers/course';
|
import { CoreCourseProvider } from '@core/course/providers/course';
|
||||||
|
import { AddonCalendarProvider } from './calendar';
|
||||||
|
import { CoreConstants } from '@core/constants';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service that provides some features regarding lists of courses and categories.
|
* Service that provides some features regarding lists of courses and categories.
|
||||||
|
@ -31,10 +33,33 @@ export class AddonCalendarHelperProvider {
|
||||||
category: 'fa-cubes'
|
category: 'fa-cubes'
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(logger: CoreLoggerProvider, private courseProvider: CoreCourseProvider) {
|
constructor(logger: CoreLoggerProvider, private courseProvider: CoreCourseProvider,
|
||||||
|
private calendarProvider: AddonCalendarProvider) {
|
||||||
this.logger = logger.getInstance('AddonCalendarHelperProvider');
|
this.logger = logger.getInstance('AddonCalendarHelperProvider');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if current user can create/edit events.
|
||||||
|
*
|
||||||
|
* @param {number} [courseId] Course ID. If not defined, site calendar.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<boolean>} Promise resolved with boolean: whether the user can create events.
|
||||||
|
*/
|
||||||
|
canEditEvents(courseId?: number, siteId?: string): Promise<boolean> {
|
||||||
|
return this.calendarProvider.canEditEvents(siteId).then((canEdit) => {
|
||||||
|
if (!canEdit) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Site allows creating events. Check if the user has permissions to do so.
|
||||||
|
return this.calendarProvider.getAllowedEventTypes(courseId, siteId).then((types) => {
|
||||||
|
return Object.keys(types).length > 0;
|
||||||
|
});
|
||||||
|
}).catch(() => {
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience function to format some event data to be rendered.
|
* Convenience function to format some event data to be rendered.
|
||||||
*
|
*
|
||||||
|
@ -46,5 +71,88 @@ export class AddonCalendarHelperProvider {
|
||||||
e.icon = this.courseProvider.getModuleIconSrc(e.modulename);
|
e.icon = this.courseProvider.getModuleIconSrc(e.modulename);
|
||||||
e.moduleIcon = e.icon;
|
e.moduleIcon = e.icon;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (e.id < 0) {
|
||||||
|
// It's an offline event, add some calculated data.
|
||||||
|
e.format = 1;
|
||||||
|
e.visible = 1;
|
||||||
|
|
||||||
|
if (e.duration == 1) {
|
||||||
|
e.timeduration = e.timedurationuntil - e.timestart;
|
||||||
|
} else if (e.duration == 2) {
|
||||||
|
e.timeduration = e.timedurationminutes * CoreConstants.SECONDS_MINUTE;
|
||||||
|
} else {
|
||||||
|
e.timeduration = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
// (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 { CoreCronHandler } from '@providers/cron';
|
||||||
|
import { AddonCalendarSyncProvider } from './calendar-sync';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Synchronization cron handler.
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class AddonCalendarSyncCronHandler implements CoreCronHandler {
|
||||||
|
name = 'AddonCalendarSyncCronHandler';
|
||||||
|
|
||||||
|
constructor(private calendarSync: AddonCalendarSyncProvider) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the process.
|
||||||
|
* Receives the ID of the site affected, undefined for all sites.
|
||||||
|
*
|
||||||
|
* @param {string} [siteId] ID of the site affected, undefined for all sites.
|
||||||
|
* @param {boolean} [force] Wether the execution is forced (manual sync).
|
||||||
|
* @return {Promise<any>} Promise resolved when done, rejected if failure.
|
||||||
|
*/
|
||||||
|
execute(siteId?: string, force?: boolean): Promise<any> {
|
||||||
|
return this.calendarSync.syncAllEvents(siteId, force);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the time between consecutive executions.
|
||||||
|
*
|
||||||
|
* @return {number} Time between consecutive executions (in ms).
|
||||||
|
*/
|
||||||
|
getInterval(): number {
|
||||||
|
return this.calendarSync.syncInterval;
|
||||||
|
}
|
||||||
|
}
|
|
@ -51,10 +51,10 @@ export class AddonModDataFieldDateComponent extends AddonModDataFieldPluginCompo
|
||||||
val = this.search['f_' + this.field.id + '_y'] ? new Date(this.search['f_' + this.field.id + '_y'] + '-' +
|
val = this.search['f_' + this.field.id + '_y'] ? new Date(this.search['f_' + this.field.id + '_y'] + '-' +
|
||||||
this.search['f_' + this.field.id + '_m'] + '-' + this.search['f_' + this.field.id + '_d']) : new Date();
|
this.search['f_' + this.field.id + '_m'] + '-' + this.search['f_' + this.field.id + '_d']) : new Date();
|
||||||
|
|
||||||
this.search['f_' + this.field.id] = val.toISOString();
|
this.search['f_' + this.field.id] = this.timeUtils.toDatetimeFormat(val.getTime());
|
||||||
} else {
|
} else {
|
||||||
val = this.value && this.value.content ? new Date(parseInt(this.value.content, 10) * 1000) : new Date();
|
val = this.value && this.value.content ? new Date(parseInt(this.value.content, 10) * 1000) : new Date();
|
||||||
val = val.toISOString();
|
val = this.timeUtils.toDatetimeFormat(val.getTime());
|
||||||
}
|
}
|
||||||
|
|
||||||
this.addControl('f_' + this.field.id, val);
|
this.addControl('f_' + this.field.id, val);
|
||||||
|
|
|
@ -15,6 +15,7 @@ import { Injector, Injectable } from '@angular/core';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { AddonModDataFieldHandler } from '../../../providers/fields-delegate';
|
import { AddonModDataFieldHandler } from '../../../providers/fields-delegate';
|
||||||
import { AddonModDataFieldDateComponent } from '../component/date';
|
import { AddonModDataFieldDateComponent } from '../component/date';
|
||||||
|
import { CoreTimeUtilsProvider } from '@providers/utils/time';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handler for date data field plugin.
|
* Handler for date data field plugin.
|
||||||
|
@ -24,7 +25,7 @@ export class AddonModDataFieldDateHandler implements AddonModDataFieldHandler {
|
||||||
name = 'AddonModDataFieldDateHandler';
|
name = 'AddonModDataFieldDateHandler';
|
||||||
type = 'date';
|
type = 'date';
|
||||||
|
|
||||||
constructor(private translate: TranslateService) { }
|
constructor(private translate: TranslateService, private timeUtils: CoreTimeUtilsProvider) { }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the Component to use to display the plugin data.
|
* Return the Component to use to display the plugin data.
|
||||||
|
@ -129,7 +130,7 @@ export class AddonModDataFieldDateHandler implements AddonModDataFieldHandler {
|
||||||
input = inputData[fieldName] && inputData[fieldName].substr(0, 10) || '';
|
input = inputData[fieldName] && inputData[fieldName].substr(0, 10) || '';
|
||||||
|
|
||||||
originalFieldData = (originalFieldData && originalFieldData.content &&
|
originalFieldData = (originalFieldData && originalFieldData.content &&
|
||||||
new Date(originalFieldData.content * 1000).toISOString().substr(0, 10)) || '';
|
this.timeUtils.toDatetimeFormat(originalFieldData.content * 1000).substr(0, 10)) || '';
|
||||||
|
|
||||||
return input != originalFieldData;
|
return input != originalFieldData;
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,16 +84,30 @@
|
||||||
"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.calendar": "Calendar",
|
"addon.calendar.calendar": "Calendar",
|
||||||
|
"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.defaultnotificationtime": "Default notification time",
|
"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.errorloadevent": "Error loading event.",
|
||||||
"addon.calendar.errorloadevents": "Error loading events.",
|
"addon.calendar.errorloadevents": "Error loading events.",
|
||||||
|
"addon.calendar.eventduration": "Duration",
|
||||||
"addon.calendar.eventendtime": "End time",
|
"addon.calendar.eventendtime": "End time",
|
||||||
|
"addon.calendar.eventname": "Event title",
|
||||||
"addon.calendar.eventstarttime": "Start time",
|
"addon.calendar.eventstarttime": "Start time",
|
||||||
|
"addon.calendar.eventtype": "Event type",
|
||||||
"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.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.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.reminders": "Reminders",
|
||||||
|
"addon.calendar.repeatevent": "Repeat this event",
|
||||||
|
"addon.calendar.repeatweeksl": "Repeat weekly, creating altogether",
|
||||||
"addon.calendar.setnewreminder": "Set a new reminder",
|
"addon.calendar.setnewreminder": "Set a new reminder",
|
||||||
"addon.calendar.typecategory": "Category event",
|
"addon.calendar.typecategory": "Category event",
|
||||||
"addon.calendar.typeclose": "Close event",
|
"addon.calendar.typeclose": "Close event",
|
||||||
|
@ -1335,6 +1349,7 @@
|
||||||
"core.course.warningmanualcompletionmodified": "The manual completion of an activity was modified on the site.",
|
"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.course.warningofflinemanualcompletiondeleted": "Some offline manual completion of course '{{name}}' has been deleted. {{error}}",
|
||||||
"core.coursedetails": "Course details",
|
"core.coursedetails": "Course details",
|
||||||
|
"core.coursenogroups": "This course doesn't have any group.",
|
||||||
"core.courses.addtofavourites": "Star this course",
|
"core.courses.addtofavourites": "Star this course",
|
||||||
"core.courses.allowguests": "This course allows guest users to enter",
|
"core.courses.allowguests": "This course allows guest users to enter",
|
||||||
"core.courses.availablecourses": "Available courses",
|
"core.courses.availablecourses": "Available courses",
|
||||||
|
@ -1465,6 +1480,7 @@
|
||||||
"core.grades.range": "Range",
|
"core.grades.range": "Range",
|
||||||
"core.grades.rank": "Rank",
|
"core.grades.rank": "Rank",
|
||||||
"core.grades.weight": "Weight",
|
"core.grades.weight": "Weight",
|
||||||
|
"core.group": "Group",
|
||||||
"core.groupsseparate": "Separate groups",
|
"core.groupsseparate": "Separate groups",
|
||||||
"core.groupsvisible": "Visible groups",
|
"core.groupsvisible": "Visible groups",
|
||||||
"core.hasdatatosync": "This {{$a}} has offline data to be synchronised.",
|
"core.hasdatatosync": "This {{$a}} has offline data to be synchronised.",
|
||||||
|
@ -1631,6 +1647,7 @@
|
||||||
"core.nopermissionerror": "Sorry, but you do not currently have permissions to do that",
|
"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.nopermissions": "Sorry, but you do not currently have permissions to do that ({{$a}}).",
|
||||||
"core.noresults": "No results",
|
"core.noresults": "No results",
|
||||||
|
"core.noselection": "No selection",
|
||||||
"core.notapplicable": "n/a",
|
"core.notapplicable": "n/a",
|
||||||
"core.notenrolledprofile": "This profile is not available because this user is not enrolled in this course.",
|
"core.notenrolledprofile": "This profile is not available because this user is not enrolled in this course.",
|
||||||
"core.notice": "Notice",
|
"core.notice": "Notice",
|
||||||
|
@ -1699,6 +1716,9 @@
|
||||||
"core.sec": "sec",
|
"core.sec": "sec",
|
||||||
"core.secs": "secs",
|
"core.secs": "secs",
|
||||||
"core.seemoredetail": "Click here to see more detail",
|
"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.send": "Send",
|
||||||
"core.sending": "Sending",
|
"core.sending": "Sending",
|
||||||
"core.serverconnection": "Error connecting to the server",
|
"core.serverconnection": "Error connecting to the server",
|
||||||
|
@ -1767,6 +1787,7 @@
|
||||||
"core.sharedfiles.sharedfiles": "Shared files",
|
"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.sharedfiles.successstorefile": "File successfully stored. Select the file to upload to your private files or use in an activity.",
|
||||||
"core.show": "Show",
|
"core.show": "Show",
|
||||||
|
"core.showless": "Show less...",
|
||||||
"core.showmore": "Show more...",
|
"core.showmore": "Show more...",
|
||||||
"core.site": "Site",
|
"core.site": "Site",
|
||||||
"core.sitehome.sitehome": "Site home",
|
"core.sitehome.sitehome": "Site home",
|
||||||
|
@ -1815,6 +1836,7 @@
|
||||||
"core.unlimited": "Unlimited",
|
"core.unlimited": "Unlimited",
|
||||||
"core.unzipping": "Unzipping",
|
"core.unzipping": "Unzipping",
|
||||||
"core.upgraderunning": "Site is being upgraded, please retry later.",
|
"core.upgraderunning": "Site is being upgraded, please retry later.",
|
||||||
|
"core.user": "User",
|
||||||
"core.user.address": "Address",
|
"core.user.address": "Address",
|
||||||
"core.user.city": "City/town",
|
"core.user.city": "City/town",
|
||||||
"core.user.contact": "Contact",
|
"core.user.contact": "Contact",
|
||||||
|
|
|
@ -48,6 +48,7 @@
|
||||||
"copiedtoclipboard": "Text copied to clipboard",
|
"copiedtoclipboard": "Text copied to clipboard",
|
||||||
"course": "Course",
|
"course": "Course",
|
||||||
"coursedetails": "Course details",
|
"coursedetails": "Course details",
|
||||||
|
"coursenogroups": "This course doesn't have any group.",
|
||||||
"currentdevice": "Current device",
|
"currentdevice": "Current device",
|
||||||
"datastoredoffline": "Data stored in the device because it couldn't be sent. It will be sent automatically later.",
|
"datastoredoffline": "Data stored in the device because it couldn't be sent. It will be sent automatically later.",
|
||||||
"date": "Date",
|
"date": "Date",
|
||||||
|
@ -102,6 +103,7 @@
|
||||||
"forcepasswordchangenotice": "You must change your password to proceed.",
|
"forcepasswordchangenotice": "You must change your password to proceed.",
|
||||||
"fulllistofcourses": "All courses",
|
"fulllistofcourses": "All courses",
|
||||||
"fullnameandsitename": "{{fullname}} ({{sitename}})",
|
"fullnameandsitename": "{{fullname}} ({{sitename}})",
|
||||||
|
"group": "Group",
|
||||||
"groupsseparate": "Separate groups",
|
"groupsseparate": "Separate groups",
|
||||||
"groupsvisible": "Visible groups",
|
"groupsvisible": "Visible groups",
|
||||||
"hasdatatosync": "This {{$a}} has offline data to be synchronised.",
|
"hasdatatosync": "This {{$a}} has offline data to be synchronised.",
|
||||||
|
@ -171,6 +173,7 @@
|
||||||
"nopermissionerror": "Sorry, but you do not currently have permissions to do that",
|
"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}}).",
|
"nopermissions": "Sorry, but you do not currently have permissions to do that ({{$a}}).",
|
||||||
"noresults": "No results",
|
"noresults": "No results",
|
||||||
|
"noselection": "No selection",
|
||||||
"notapplicable": "n/a",
|
"notapplicable": "n/a",
|
||||||
"notenrolledprofile": "This profile is not available because this user is not enrolled in this course.",
|
"notenrolledprofile": "This profile is not available because this user is not enrolled in this course.",
|
||||||
"notice": "Notice",
|
"notice": "Notice",
|
||||||
|
@ -211,10 +214,14 @@
|
||||||
"sec": "sec",
|
"sec": "sec",
|
||||||
"secs": "secs",
|
"secs": "secs",
|
||||||
"seemoredetail": "Click here to see more detail",
|
"seemoredetail": "Click here to see more detail",
|
||||||
|
"selectacategory": "Please select a category",
|
||||||
|
"selectacourse": "Select a course",
|
||||||
|
"selectagroup": "Select a group",
|
||||||
"send": "Send",
|
"send": "Send",
|
||||||
"sending": "Sending",
|
"sending": "Sending",
|
||||||
"serverconnection": "Error connecting to the server",
|
"serverconnection": "Error connecting to the server",
|
||||||
"show": "Show",
|
"show": "Show",
|
||||||
|
"showless": "Show less...",
|
||||||
"showmore": "Show more...",
|
"showmore": "Show more...",
|
||||||
"site": "Site",
|
"site": "Site",
|
||||||
"sitemaintenance": "The site is undergoing maintenance and is currently not available",
|
"sitemaintenance": "The site is undergoing maintenance and is currently not available",
|
||||||
|
@ -261,6 +268,7 @@
|
||||||
"unlimited": "Unlimited",
|
"unlimited": "Unlimited",
|
||||||
"unzipping": "Unzipping",
|
"unzipping": "Unzipping",
|
||||||
"upgraderunning": "Site is being upgraded, please retry later.",
|
"upgraderunning": "Site is being upgraded, please retry later.",
|
||||||
|
"user": "User",
|
||||||
"userdeleted": "This user account has been deleted",
|
"userdeleted": "This user account has been deleted",
|
||||||
"userdetails": "User details",
|
"userdetails": "User details",
|
||||||
"usernotfullysetup": "User not fully set-up",
|
"usernotfullysetup": "User not fully set-up",
|
||||||
|
|
|
@ -299,6 +299,33 @@ export class CoreTimeUtilsProvider {
|
||||||
return moment(timestamp).format(format);
|
return moment(timestamp).format(format);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a timestamp to the format to set to a datetime input.
|
||||||
|
*
|
||||||
|
* @param {number} [timestamp] Timestamp to convert (in ms). If not provided, current time.
|
||||||
|
* @return {string} Formatted time.
|
||||||
|
*/
|
||||||
|
toDatetimeFormat(timestamp?: number): string {
|
||||||
|
timestamp = timestamp || Date.now();
|
||||||
|
|
||||||
|
return this.userDate(timestamp, 'YYYY-MM-DDTHH:mm:ss.SSS', false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert the value of a ion-datetime to a Date.
|
||||||
|
*
|
||||||
|
* @param {string} value Value of ion-datetime.
|
||||||
|
* @return {Date} Date.
|
||||||
|
*/
|
||||||
|
datetimeToDate(value: string): Date {
|
||||||
|
if (typeof value == 'string' && value.slice(-1) == 'Z') {
|
||||||
|
// The value shoudln't have the timezone because it causes problems, remove it.
|
||||||
|
value = value.substr(0, value.length - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Date(value);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert a text into user timezone timestamp.
|
* Convert a text into user timezone timestamp.
|
||||||
*
|
*
|
||||||
|
|
|
@ -403,13 +403,15 @@ export class CoreUtilsProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Flatten an object, moving subobjects' properties to the first level using dot notation. E.g.:
|
* Flatten an object, moving subobjects' properties to the first level.
|
||||||
* {a: {b: 1, c: 2}, d: 3} -> {'a.b': 1, 'a.c': 2, d: 3}
|
* 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.
|
* @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 = {};
|
const toReturn = {};
|
||||||
|
|
||||||
for (const name in obj) {
|
for (const name in obj) {
|
||||||
|
@ -425,7 +427,8 @@ export class CoreUtilsProvider {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
toReturn[name + '.' + subName] = flatObject[subName];
|
const newName = useDotNotation ? name + '.' + subName : name + '[' + subName + ']';
|
||||||
|
toReturn[newName] = flatObject[subName];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
toReturn[name] = value;
|
toReturn[name] = value;
|
||||||
|
@ -1078,6 +1081,37 @@ export class CoreUtilsProvider {
|
||||||
return mapped;
|
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.
|
* Add a prefix to all the keys in an object.
|
||||||
*
|
*
|
||||||
|
|
Loading…
Reference in New Issue