commit
ece285db4e
60
gulpfile.js
60
gulpfile.js
|
@ -59,39 +59,39 @@ function treatMergedData(data) {
|
||||||
var mergedOrdered = {};
|
var mergedOrdered = {};
|
||||||
|
|
||||||
for (var filepath in data) {
|
for (var filepath in data) {
|
||||||
|
var pathSplit = filepath.split('/');
|
||||||
|
|
||||||
if (filepath.indexOf('lang/') === 0 || filepath.indexOf('core/lang') === 0) {
|
pathSplit.pop();
|
||||||
|
|
||||||
addProperties(merged, data[filepath], 'core.');
|
switch (pathSplit[0]) {
|
||||||
|
case 'lang':
|
||||||
|
prefix = 'core';
|
||||||
|
break;
|
||||||
|
case 'core':
|
||||||
|
if (pathSplit[1] == 'lang') {
|
||||||
|
// Not used right now.
|
||||||
|
prefix = 'core';
|
||||||
|
} else {
|
||||||
|
prefix = 'core.' + pathSplit[1];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'addon':
|
||||||
|
// Remove final item 'lang'.
|
||||||
|
pathSplit.pop();
|
||||||
|
// Remove first item 'addon'.
|
||||||
|
pathSplit.shift();
|
||||||
|
|
||||||
} else if (filepath.indexOf('core/') === 0) {
|
// For subplugins. We'll use plugin_subfolder_subfolder2_...
|
||||||
|
// E.g. 'mod_assign_feedback_comments'.
|
||||||
var componentName = filepath.replace('core/', '');
|
prefix = 'addon.' + pathSplit.join('_');
|
||||||
componentName = componentName.substr(0, componentName.indexOf('/'));
|
break;
|
||||||
addProperties(merged, data[filepath], 'core.'+componentName+'.');
|
case 'assets':
|
||||||
|
prefix = 'assets.' + pathSplit[1];
|
||||||
} else if (filepath.indexOf('addons') === 0) {
|
break;
|
||||||
|
}
|
||||||
var split = filepath.split('/'),
|
|
||||||
pluginName = split[1],
|
|
||||||
index = 2;
|
|
||||||
|
|
||||||
// Check if it's a subplugin. If so, we'll use plugin_subfolder_subfolder2_...
|
|
||||||
// E.g. 'mod_assign_feedback_comments'.
|
|
||||||
while (split[index] && split[index] != 'lang') {
|
|
||||||
pluginName = pluginName + '_' + split[index];
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
addProperties(merged, data[filepath], 'mma.'+pluginName+'.');
|
|
||||||
|
|
||||||
} else if (filepath.indexOf('assets/countries') === 0) {
|
|
||||||
|
|
||||||
addProperties(merged, data[filepath], 'core.country-');
|
|
||||||
|
|
||||||
} else if (filepath.indexOf('assets/mimetypes') === 0) {
|
|
||||||
|
|
||||||
addProperties(merged, data[filepath], 'core.mimetype-');
|
|
||||||
|
|
||||||
|
if (prefix) {
|
||||||
|
addProperties(merged, data[filepath], prefix + '.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -181,7 +181,7 @@ var appLangFiles = ['ar.json', 'bg.json', 'ca.json', 'cs.json', 'da.json', 'de.j
|
||||||
lang: [
|
lang: [
|
||||||
'./src/lang/',
|
'./src/lang/',
|
||||||
'./src/core/**/lang/',
|
'./src/core/**/lang/',
|
||||||
'./src/addons/**/lang/',
|
'./src/addon/**/lang/',
|
||||||
'./src/assets/countries/',
|
'./src/assets/countries/',
|
||||||
'./src/assets/mimetypes/'
|
'./src/assets/mimetypes/'
|
||||||
],
|
],
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
// (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 { AddonCalendarProvider } from './providers/calendar';
|
||||||
|
import { AddonCalendarHelperProvider } from './providers/helper';
|
||||||
|
import { AddonCalendarMainMenuHandler } from './providers/handlers';
|
||||||
|
import { CoreMainMenuDelegate } from '../../core/mainmenu/providers/delegate';
|
||||||
|
import { CoreInitDelegate } from '../../providers/init';
|
||||||
|
import { CoreLocalNotificationsProvider } from '../../providers/local-notifications';
|
||||||
|
import { CoreLoginHelperProvider } from '../../core/login/providers/helper';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
AddonCalendarProvider,
|
||||||
|
AddonCalendarHelperProvider,
|
||||||
|
AddonCalendarMainMenuHandler
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class AddonCalendarModule {
|
||||||
|
constructor(mainMenuDelegate: CoreMainMenuDelegate, calendarHandler: AddonCalendarMainMenuHandler,
|
||||||
|
initDelegate: CoreInitDelegate, calendarProvider: AddonCalendarProvider, loginHelper: CoreLoginHelperProvider,
|
||||||
|
localNotificationsProvider: CoreLocalNotificationsProvider) {
|
||||||
|
mainMenuDelegate.registerHandler(calendarHandler);
|
||||||
|
|
||||||
|
initDelegate.ready().then(() => {
|
||||||
|
calendarProvider.scheduleAllSitesEventsNotifications();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
localNotificationsProvider.registerClick(AddonCalendarProvider.COMPONENT, (data) => {
|
||||||
|
if (data.eventid) {
|
||||||
|
initDelegate.ready().then(() => {
|
||||||
|
calendarProvider.isDisabled(data.siteId).then(function(disabled) {
|
||||||
|
if (disabled) {
|
||||||
|
// The calendar is disabled in the site, don't open it.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
loginHelper.redirect('AddonCalendarListPage', {eventid: data.eventid}, data.siteId);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"calendar": "Calendar",
|
||||||
|
"calendarevents": "Calendar events",
|
||||||
|
"defaultnotificationtime": "Default notification time",
|
||||||
|
"errorloadevent": "Error loading event.",
|
||||||
|
"errorloadevents": "Error loading events.",
|
||||||
|
"eventendtime": "End time",
|
||||||
|
"eventstarttime": "Start time",
|
||||||
|
"noevents": "There are no events",
|
||||||
|
"notifications": "Notifications",
|
||||||
|
"typeclose": "Close event",
|
||||||
|
"typecourse": "Course event",
|
||||||
|
"typecategory": "Category event",
|
||||||
|
"typedue": "Due event",
|
||||||
|
"typegradingdue": "Grading due event",
|
||||||
|
"typegroup": "Group event",
|
||||||
|
"typeopen": "Open event",
|
||||||
|
"typesite": "Site event",
|
||||||
|
"typeuser": "User event"
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
<ion-header>
|
||||||
|
<ion-navbar>
|
||||||
|
<ion-title><core-format-text [text]="title"></core-format-text></ion-title>
|
||||||
|
</ion-navbar>
|
||||||
|
</ion-header>
|
||||||
|
<ion-content>
|
||||||
|
<ion-refresher [enabled]="eventLoaded" (ionRefresh)="refreshEvent($event)">
|
||||||
|
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||||
|
</ion-refresher>
|
||||||
|
<core-loading [hideUntil]="eventLoaded" class="core-loading-center">
|
||||||
|
<ion-card>
|
||||||
|
<ion-card-content>
|
||||||
|
<ion-card-title text-wrap>
|
||||||
|
<ion-icon *ngIf="!event.moduleIcon" name="{{event.icon}}" item-start></ion-icon>
|
||||||
|
<core-format-text [text]="event.name"></core-format-text>
|
||||||
|
</ion-card-title>
|
||||||
|
<ion-item text-wrap>
|
||||||
|
<h2>{{ 'addon.calendar.eventstarttime' | translate}}</h2>
|
||||||
|
<p>{{ event.timestart | coreToLocaleString }}</p>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item text-wrap *ngIf="event.timeduration > 0">
|
||||||
|
<h2>{{ 'addon.calendar.eventendtime' | translate}}</h2>
|
||||||
|
<p>{{ (event.timestart + event.timeduration) | coreToLocaleString }}</p>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item text-wrap *ngIf="courseName">
|
||||||
|
<h2>{{ 'core.course' | translate}}</h2>
|
||||||
|
<p><core-format-text [text]="courseName"></core-format-text></p>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item text-wrap *ngIf="event.moduleIcon">
|
||||||
|
<img *ngIf="event.moduleIcon" src="{{event.moduleIcon}}" item-start> {{event.moduleName}}
|
||||||
|
</ion-item>
|
||||||
|
<ion-item>
|
||||||
|
<p text-wrap *ngIf="event.description">
|
||||||
|
<core-format-text [text]="event.description"></core-format-text>
|
||||||
|
</p>
|
||||||
|
</ion-item>
|
||||||
|
</ion-card-content>
|
||||||
|
</ion-card>
|
||||||
|
|
||||||
|
<ion-card list *ngIf="notificationsEnabled">
|
||||||
|
<ion-item>
|
||||||
|
<ion-label>{{ 'addon.calendar.notifications' | translate }}</ion-label>
|
||||||
|
<ion-select [(ngModel)]="notificationTime" (ionChange)="updateNotificationTime($event)">
|
||||||
|
<ion-option value="-1">{{ 'core.defaultvalue' | translate :{$a: defaultTimeReadable} }}</ion-option>
|
||||||
|
<ion-option value="0">{{ 'core.settings.disabled' | translate }}</ion-option>
|
||||||
|
<ion-option value="10">{{ 600 | coreDuration }}</ion-option>
|
||||||
|
<ion-option value="30">{{ 1800 | coreDuration }}</ion-option>
|
||||||
|
<ion-option value="60">{{ 3600 | coreDuration }}</ion-option>
|
||||||
|
<ion-option value="120">{{ 7200 | coreDuration }}</ion-option>
|
||||||
|
<ion-option value="360">{{ 21600 | coreDuration }}</ion-option>
|
||||||
|
<ion-option value="720">{{ 43200 | coreDuration }}</ion-option>
|
||||||
|
<ion-option value="1440">{{ 86400 | coreDuration }}</ion-option>
|
||||||
|
</ion-select>
|
||||||
|
</ion-item>
|
||||||
|
</ion-card>
|
||||||
|
</core-loading>
|
||||||
|
</ion-content>
|
|
@ -0,0 +1,35 @@
|
||||||
|
// (C) Copyright 2015 Martin Dougiamas
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { IonicPageModule } from 'ionic-angular';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { CoreComponentsModule } from '../../../../components/components.module';
|
||||||
|
import { CoreDirectivesModule } from '../../../../directives/directives.module';
|
||||||
|
import { CorePipesModule } from '../../../../pipes/pipes.module';
|
||||||
|
import { AddonCalendarEventPage } from './event';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
AddonCalendarEventPage,
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
CoreComponentsModule,
|
||||||
|
CoreDirectivesModule,
|
||||||
|
CorePipesModule,
|
||||||
|
IonicPageModule.forChild(AddonCalendarEventPage),
|
||||||
|
TranslateModule.forChild()
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class AddonCalendarEventPageModule {}
|
|
@ -0,0 +1,3 @@
|
||||||
|
page-addon-calendar-event {
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,140 @@
|
||||||
|
// (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, ViewChild } from '@angular/core';
|
||||||
|
import { IonicPage, Content, NavParams } from 'ionic-angular';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { AddonCalendarProvider } from '../../providers/calendar';
|
||||||
|
import { AddonCalendarHelperProvider } from '../../providers/helper';
|
||||||
|
import { CoreCoursesProvider } from '../../../../core/courses/providers/courses';
|
||||||
|
import { CoreDomUtilsProvider } from '../../../../providers/utils/dom';
|
||||||
|
import { CoreSitesProvider } from '../../../../providers/sites';
|
||||||
|
import { CoreLocalNotificationsProvider } from '../../../../providers/local-notifications';
|
||||||
|
//import { CoreCourseProvider } from '../../../core/course/providers/course';
|
||||||
|
import * as moment from 'moment';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Page that displays a single calendar event.
|
||||||
|
*/
|
||||||
|
@IonicPage({segment: "addon-calendar-event"})
|
||||||
|
@Component({
|
||||||
|
selector: 'page-addon-calendar-event',
|
||||||
|
templateUrl: 'event.html',
|
||||||
|
})
|
||||||
|
export class AddonCalendarEventPage {
|
||||||
|
@ViewChild(Content) content: Content;
|
||||||
|
|
||||||
|
protected eventId;
|
||||||
|
protected siteHomeId: number;
|
||||||
|
eventLoaded: boolean;
|
||||||
|
notificationTime: number;
|
||||||
|
defaultTimeReadable: string;
|
||||||
|
event: any = {};
|
||||||
|
title: string;
|
||||||
|
courseName: string;
|
||||||
|
notificationsEnabled = false;
|
||||||
|
|
||||||
|
constructor(private translate: TranslateService, private calendarProvider: AddonCalendarProvider, private navParams: NavParams,
|
||||||
|
private domUtils: CoreDomUtilsProvider, private coursesProvider: CoreCoursesProvider,
|
||||||
|
private calendarHelper: AddonCalendarHelperProvider, private sitesProvider: CoreSitesProvider,
|
||||||
|
private localNotificationsProvider: CoreLocalNotificationsProvider/*, private courseProvider: CoreCourseProvider*/) {
|
||||||
|
|
||||||
|
this.eventId = navParams.get('id');
|
||||||
|
this.notificationsEnabled = localNotificationsProvider.isAvailable();
|
||||||
|
this.siteHomeId = sitesProvider.getCurrentSite().getSiteHomeId();
|
||||||
|
if (this.notificationsEnabled) {
|
||||||
|
this.calendarProvider.getEventNotificationTimeOption(this.eventId).then((notificationTime) => {
|
||||||
|
this.notificationTime = notificationTime;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.calendarProvider.getDefaultNotificationTime().then((defaultTime) => {
|
||||||
|
if (defaultTime === 0) {
|
||||||
|
// Disabled by default.
|
||||||
|
this.defaultTimeReadable = this.translate.instant('core.settings.disabled');
|
||||||
|
} else {
|
||||||
|
this.defaultTimeReadable = moment.duration(defaultTime * 60 * 1000).humanize();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View loaded.
|
||||||
|
*/
|
||||||
|
ionViewDidLoad() {
|
||||||
|
this.fetchEvent().finally(() => {
|
||||||
|
this.eventLoaded = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateNotificationTime() {
|
||||||
|
if (!isNaN(this.notificationTime) && this.event && this.event.id) {
|
||||||
|
this.calendarProvider.updateNotificationTime(this.event, this.notificationTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches the event and updates the view.
|
||||||
|
*/
|
||||||
|
fetchEvent() {
|
||||||
|
return this.calendarProvider.getEvent(this.eventId).then((event) => {
|
||||||
|
this.calendarHelper.formatEventData(event);
|
||||||
|
this.event = event;
|
||||||
|
|
||||||
|
// Guess event title.
|
||||||
|
let title = this.translate.instant('addon.calendar.type' + event.eventtype);
|
||||||
|
if (event.moduleIcon) {
|
||||||
|
// @todo: It's a module event, translate the module name to the current language.
|
||||||
|
let name = "" //this.courseProvider.translateModuleName(event.modulename);
|
||||||
|
if (name.indexOf('core.mod_') === -1) {
|
||||||
|
event.moduleName = name;
|
||||||
|
}
|
||||||
|
if (title == 'addon.calendar.type' + event.eventtype) {
|
||||||
|
title = this.translate.instant('core.mod_'+ event.modulename + '.' + event.eventtype);
|
||||||
|
|
||||||
|
if (title == 'core.mod_'+ event.modulename + '.' + event.eventtype) {
|
||||||
|
title = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (title == 'addon.calendar.type' + event.eventtype) {
|
||||||
|
title = event.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.title = title;
|
||||||
|
|
||||||
|
if (event.courseid != this.siteHomeId) {
|
||||||
|
// It's a course event, retrieve the course name.
|
||||||
|
return this.coursesProvider.getUserCourse(event.courseid, true).then((course) => {
|
||||||
|
this.courseName = course.fullname;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).catch((error) => {
|
||||||
|
this.domUtils.showErrorModalDefault(error, 'addon.calendar.errorloadevent', true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh the event.
|
||||||
|
*
|
||||||
|
* @param {any} refresher Refresher.
|
||||||
|
*/
|
||||||
|
refreshEvent(refresher: any) {
|
||||||
|
this.calendarProvider.invalidateEvent(this.eventId).finally(() => {
|
||||||
|
this.fetchEvent().finally(() => {
|
||||||
|
refresher.complete();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
<ion-header>
|
||||||
|
<ion-navbar>
|
||||||
|
<ion-title>{{ 'addon.calendar.calendarevents' | translate }}</ion-title>
|
||||||
|
<ion-buttons end>
|
||||||
|
<button *ngIf="courses && courses.length" ion-button icon-only (click)="openCourseFilter($event)" [attr.aria-label]="'core.courses.filter' | translate">
|
||||||
|
<ion-icon name="funnel"></ion-icon>
|
||||||
|
</button>
|
||||||
|
<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>
|
||||||
|
</ion-buttons>
|
||||||
|
</ion-navbar>
|
||||||
|
</ion-header>
|
||||||
|
<core-split-view>
|
||||||
|
<ion-content>
|
||||||
|
<ion-refresher [enabled]="eventsLoaded" (ionRefresh)="refreshEvents($event)">
|
||||||
|
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||||
|
</ion-refresher>
|
||||||
|
<core-loading [hideUntil]="eventsLoaded" class="core-loading-center">
|
||||||
|
<core-empty-box *ngIf="!filteredEvents || !filteredEvents.length" icon="calendar" [message]="'addon.calendar.noevents' | translate">
|
||||||
|
</core-empty-box>
|
||||||
|
|
||||||
|
<ion-list *ngIf="filteredEvents && filteredEvents.length">
|
||||||
|
<a ion-item text-wrap *ngFor="let event of filteredEvents" [title]="event.name" (click)="gotoEvent(event.id)" [class.core-split-item-selected]="event.id == eventId">
|
||||||
|
<img *ngIf="event.moduleIcon" src="{{event.moduleIcon}}" item-start>
|
||||||
|
<ion-icon *ngIf="!event.moduleIcon" name="{{event.icon}}" item-start></ion-icon>
|
||||||
|
<h2><core-format-text [text]="event.name"></core-format-text></h2>
|
||||||
|
<p>{{ event.timestart | coreToLocaleString }}</p>
|
||||||
|
</a>
|
||||||
|
</ion-list>
|
||||||
|
|
||||||
|
<ion-infinite-scroll [enabled]="canLoadMore" (ionInfinite)="$event.waitFor(fetchEvents())">
|
||||||
|
<ion-infinite-scroll-content></ion-infinite-scroll-content>
|
||||||
|
</ion-infinite-scroll>
|
||||||
|
</core-loading>
|
||||||
|
</ion-content>
|
||||||
|
</core-split-view>
|
|
@ -0,0 +1,35 @@
|
||||||
|
// (C) Copyright 2015 Martin Dougiamas
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { IonicPageModule } from 'ionic-angular';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { CoreComponentsModule } from '../../../../components/components.module';
|
||||||
|
import { CoreDirectivesModule } from '../../../../directives/directives.module';
|
||||||
|
import { CorePipesModule } from '../../../../pipes/pipes.module';
|
||||||
|
import { AddonCalendarListPage } from './list';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
AddonCalendarListPage,
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
CoreComponentsModule,
|
||||||
|
CoreDirectivesModule,
|
||||||
|
CorePipesModule,
|
||||||
|
IonicPageModule.forChild(AddonCalendarListPage),
|
||||||
|
TranslateModule.forChild()
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class AddonCalendarListPageModule {}
|
|
@ -0,0 +1,3 @@
|
||||||
|
page-addon-calendar-list {
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,327 @@
|
||||||
|
// (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, ViewChild, OnDestroy } from '@angular/core';
|
||||||
|
import { IonicPage, Content, PopoverController, NavParams, NavController } from 'ionic-angular';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { AddonCalendarProvider } from '../../providers/calendar';
|
||||||
|
import { AddonCalendarHelperProvider } from '../../providers/helper';
|
||||||
|
import { CoreCoursesProvider } from '../../../../core/courses/providers/courses';
|
||||||
|
import { CoreDomUtilsProvider } from '../../../../providers/utils/dom';
|
||||||
|
import { CoreUtilsProvider } from '../../../../providers/utils/utils';
|
||||||
|
import { CoreSitesProvider } from '../../../../providers/sites';
|
||||||
|
import { CoreLocalNotificationsProvider } from '../../../../providers/local-notifications';
|
||||||
|
import { CoreCoursePickerMenuPopoverComponent } from '../../../../components/course-picker-menu/course-picker-menu-popover';
|
||||||
|
import { CoreEventsProvider } from '../../../../providers/events';
|
||||||
|
import { CoreAppProvider } from '../../../../providers/app';
|
||||||
|
import { CoreSplitViewComponent } from '../../../../components/split-view/split-view';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Page that displays the list of calendar events.
|
||||||
|
*/
|
||||||
|
@IonicPage({segment: "addon-calendar-list"})
|
||||||
|
@Component({
|
||||||
|
selector: 'page-addon-calendar-list',
|
||||||
|
templateUrl: 'list.html',
|
||||||
|
})
|
||||||
|
export class AddonCalendarListPage implements OnDestroy {
|
||||||
|
@ViewChild(Content) content: Content;
|
||||||
|
@ViewChild(CoreSplitViewComponent) splitviewCtrl: CoreSplitViewComponent;
|
||||||
|
|
||||||
|
protected daysLoaded = 0;
|
||||||
|
protected emptyEventsTimes = 0; // Variable to identify consecutive calls returning 0 events.
|
||||||
|
protected categoriesRetrieved = false;
|
||||||
|
protected getCategories = false;
|
||||||
|
protected allCourses = {
|
||||||
|
id: -1,
|
||||||
|
fullname: this.translate.instant('core.fulllistofcourses'),
|
||||||
|
category: -1
|
||||||
|
};
|
||||||
|
protected categories = {};
|
||||||
|
protected siteHomeId: number;
|
||||||
|
protected obsDefaultTimeChange: any;
|
||||||
|
protected eventId: number;
|
||||||
|
|
||||||
|
courses: any[];
|
||||||
|
eventsLoaded = false;
|
||||||
|
events = [];
|
||||||
|
notificationsEnabled = false;
|
||||||
|
filteredEvents = [];
|
||||||
|
canLoadMore = false;
|
||||||
|
filter = {
|
||||||
|
course: this.allCourses
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(private translate: TranslateService, private calendarProvider: AddonCalendarProvider, private navParams: NavParams,
|
||||||
|
private domUtils: CoreDomUtilsProvider, private coursesProvider: CoreCoursesProvider, private utils: CoreUtilsProvider,
|
||||||
|
private calendarHelper: AddonCalendarHelperProvider, private sitesProvider: CoreSitesProvider,
|
||||||
|
private localNotificationsProvider: CoreLocalNotificationsProvider, private popoverCtrl: PopoverController,
|
||||||
|
private eventsProvider: CoreEventsProvider, private navCtrl: NavController, private appProvider: CoreAppProvider) {
|
||||||
|
|
||||||
|
this.siteHomeId = sitesProvider.getCurrentSite().getSiteHomeId();
|
||||||
|
this.notificationsEnabled = localNotificationsProvider.isAvailable();
|
||||||
|
if (this.notificationsEnabled) {
|
||||||
|
// Re-schedule events if default time changes.
|
||||||
|
this.obsDefaultTimeChange = eventsProvider.on(AddonCalendarProvider.DEFAULT_NOTIFICATION_TIME_CHANGED, () => {
|
||||||
|
calendarProvider.scheduleEventsNotifications(this.events);
|
||||||
|
}, sitesProvider.getCurrentSiteId());
|
||||||
|
}
|
||||||
|
|
||||||
|
this.eventId = navParams.get('eventid') || false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View loaded.
|
||||||
|
*/
|
||||||
|
ionViewDidLoad() {
|
||||||
|
if (this.eventId) {
|
||||||
|
// There is an event to load, open the event in a new state.
|
||||||
|
this.gotoEvent(this.eventId);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.fetchData().then(() => {
|
||||||
|
if (!this.eventId && this.splitviewCtrl.isOn() && this.events.length > 0) {
|
||||||
|
// Take first and load it.
|
||||||
|
this.gotoEvent(this.events[0].id);
|
||||||
|
}
|
||||||
|
}).finally(() => {
|
||||||
|
this.eventsLoaded = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch all the data required for the view.
|
||||||
|
*
|
||||||
|
* @param {boolean} [refresh] Empty events array first.
|
||||||
|
*/
|
||||||
|
fetchData(refresh = false) {
|
||||||
|
this.daysLoaded = 0;
|
||||||
|
this.emptyEventsTimes = 0;
|
||||||
|
|
||||||
|
// Load courses for the popover.
|
||||||
|
return this.coursesProvider.getUserCourses(false).then((courses) => {
|
||||||
|
// Add "All courses".
|
||||||
|
courses.unshift(this.allCourses);
|
||||||
|
this.courses = courses;
|
||||||
|
return this.fetchEvents(refresh);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches the events and updates the view.
|
||||||
|
*
|
||||||
|
* @param {boolean} [refresh] Empty events array first.
|
||||||
|
*/
|
||||||
|
fetchEvents(refresh = false) {
|
||||||
|
return this.calendarProvider.getEventsList(this.daysLoaded, AddonCalendarProvider.DAYS_INTERVAL).then((events) => {
|
||||||
|
this.daysLoaded += AddonCalendarProvider.DAYS_INTERVAL;
|
||||||
|
if (events.length === 0) {
|
||||||
|
this.emptyEventsTimes++;
|
||||||
|
if (this.emptyEventsTimes > 5) { // Stop execution if we retrieve empty list 6 consecutive times.
|
||||||
|
this.canLoadMore = false;
|
||||||
|
if (refresh) {
|
||||||
|
this.events = [];
|
||||||
|
this.filteredEvents = [];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// No events returned, load next events.
|
||||||
|
return this.fetchEvents();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Sort the events by timestart, they're ordered by id.
|
||||||
|
events.sort((a, b) => {
|
||||||
|
return a.timestart - b.timestart;
|
||||||
|
});
|
||||||
|
|
||||||
|
events.forEach(this.calendarHelper.formatEventData.bind(this.calendarHelper));
|
||||||
|
this.getCategories = this.shouldLoadCategories(events);
|
||||||
|
|
||||||
|
if (refresh) {
|
||||||
|
this.events = events;
|
||||||
|
} else {
|
||||||
|
// Filter events with same ID. Repeated events are returned once per WS call, show them only once.
|
||||||
|
this.events = this.utils.mergeArraysWithoutDuplicates(this.events, events, 'id');
|
||||||
|
}
|
||||||
|
this.filteredEvents = this.getFilteredEvents();
|
||||||
|
this.canLoadMore = true;
|
||||||
|
|
||||||
|
// Schedule notifications for the events retrieved (might have new events).
|
||||||
|
this.calendarProvider.scheduleEventsNotifications(this.events);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resize the content so infinite loading is able to calculate if it should load more items or not.
|
||||||
|
// @todo: Infinite loading is not working if content is not high enough.
|
||||||
|
this.content.resize();
|
||||||
|
}).catch((error) => {
|
||||||
|
this.domUtils.showErrorModalDefault(error, 'addon.calendar.errorloadevents', true);
|
||||||
|
this.canLoadMore = false; // Set to false to prevent infinite calls with infinite-loading.
|
||||||
|
}).then(() => {
|
||||||
|
// Success retrieving events. Get categories if needed.
|
||||||
|
if (this.getCategories) {
|
||||||
|
this.getCategories = false;
|
||||||
|
return this.loadCategories();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get filtered events.
|
||||||
|
*/
|
||||||
|
protected getFilteredEvents() {
|
||||||
|
if (this.filter.course.id == -1) {
|
||||||
|
// No filter, display everything.
|
||||||
|
return this.events;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.events.filter(this.shouldDisplayEvent.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if an event should be displayed based on the filter.
|
||||||
|
*
|
||||||
|
* @param {any} event Event object.
|
||||||
|
*/
|
||||||
|
protected shouldDisplayEvent(event: any) {
|
||||||
|
if (event.eventtype == 'user' || event.eventtype == 'site') {
|
||||||
|
// User or site event, display it.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.eventtype == 'category') {
|
||||||
|
if (!event.categoryid || !Object.keys(this.categories).length) {
|
||||||
|
// We can't tell if the course belongs to the category, display them all.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (event.categoryid == this.filter.course.category) {
|
||||||
|
// The event is in the same category as the course, display it.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check parent categories.
|
||||||
|
let category = this.categories[this.filter.course.category];
|
||||||
|
while (category) {
|
||||||
|
if (!category.parent) {
|
||||||
|
// Category doesn't have parent, stop.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.categoryid == category.parent) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
category = this.categories[category.parent];
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show the event if it is from site home or if it matches the selected course.
|
||||||
|
return event.courseid === this.siteHomeId || event.courseid == this.filter.course.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns if the current state should load categories or not.
|
||||||
|
* @param {any[]} events Events to parse.
|
||||||
|
* @return {boolean} True if categories should be loaded.
|
||||||
|
*/
|
||||||
|
protected shouldLoadCategories(events: any[]) : boolean {
|
||||||
|
if (this.categoriesRetrieved || this.getCategories) {
|
||||||
|
// Use previous value
|
||||||
|
return this.getCategories;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Categories not loaded yet. We should get them if there's any category event.
|
||||||
|
let found = events.some(event => event.categoryid != 'undefined' && event.categoryid > 0);
|
||||||
|
return found || this.getCategories;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load categories to be able to filter events.
|
||||||
|
*/
|
||||||
|
protected loadCategories() {
|
||||||
|
return this.coursesProvider.getCategories(0, true).then((cats) => {
|
||||||
|
this.categoriesRetrieved = true;
|
||||||
|
this.categories = {};
|
||||||
|
// Index categories by ID.
|
||||||
|
cats.forEach((category) => {
|
||||||
|
this.categories[category.id] = category;
|
||||||
|
});
|
||||||
|
}).catch(() => {
|
||||||
|
// Ignore errors.
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh the events.
|
||||||
|
*
|
||||||
|
* @param {any} refresher Refresher.
|
||||||
|
*/
|
||||||
|
refreshEvents(refresher: any) {
|
||||||
|
let promises = [];
|
||||||
|
|
||||||
|
promises.push(this.calendarProvider.invalidateEventsList(this.courses));
|
||||||
|
|
||||||
|
if (this.categoriesRetrieved) {
|
||||||
|
promises.push(this.coursesProvider.invalidateCategories(0, true));
|
||||||
|
this.categoriesRetrieved = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Promise.all(promises).finally(() => {
|
||||||
|
this.fetchData(true).finally(() => {
|
||||||
|
refresher.complete();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the context menu.
|
||||||
|
*
|
||||||
|
* @param {MouseEvent} event Event.
|
||||||
|
*/
|
||||||
|
openCourseFilter(event: MouseEvent) : void {
|
||||||
|
let popover = this.popoverCtrl.create(CoreCoursePickerMenuPopoverComponent, {courses: this.courses,
|
||||||
|
courseId: this.filter.course.id});
|
||||||
|
popover.onDidDismiss((course) => {
|
||||||
|
if (course) {
|
||||||
|
this.filter.course = course;
|
||||||
|
this.content.scrollToTop();
|
||||||
|
this.filteredEvents = this.getFilteredEvents();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
popover.present({
|
||||||
|
ev: event
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open calendar events settings.
|
||||||
|
*/
|
||||||
|
openSettings() {
|
||||||
|
this.navCtrl.push('AddonCalendarSettingsPage');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigate to a particular event.
|
||||||
|
*/
|
||||||
|
gotoEvent(eventId) {
|
||||||
|
this.eventId = eventId;
|
||||||
|
this.splitviewCtrl.push('AddonCalendarEventPage', {id: eventId});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Page destroyed.
|
||||||
|
*/
|
||||||
|
ngOnDestroy() {
|
||||||
|
this.obsDefaultTimeChange && this.obsDefaultTimeChange.off();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
<ion-header>
|
||||||
|
<ion-navbar>
|
||||||
|
<ion-title>{{ 'core.settings.settings' | translate }}</ion-title>
|
||||||
|
</ion-navbar>
|
||||||
|
</ion-header>
|
||||||
|
<ion-content>
|
||||||
|
<ion-list>
|
||||||
|
<ion-item>
|
||||||
|
<ion-label>{{ 'addon.calendar.defaultnotificationtime' | translate }}</ion-label>
|
||||||
|
<ion-select [(ngModel)]="defaultTime" (ionChange)="updateDefaultTime($event)">
|
||||||
|
<ion-option value="0">{{ 'core.settings.disabled' | translate }}</ion-option>
|
||||||
|
<ion-option value="10">{{ 600 | coreDuration }}</ion-option>
|
||||||
|
<ion-option value="30">{{ 1800 | coreDuration }}</ion-option>
|
||||||
|
<ion-option value="60">{{ 3600 | coreDuration }}</ion-option>
|
||||||
|
<ion-option value="120">{{ 7200 | coreDuration }}</ion-option>
|
||||||
|
<ion-option value="360">{{ 21600 | coreDuration }}</ion-option>
|
||||||
|
<ion-option value="720">{{ 43200 | coreDuration }}</ion-option>
|
||||||
|
<ion-option value="1440">{{ 86400 | coreDuration }}</ion-option>
|
||||||
|
</ion-select>
|
||||||
|
</ion-item>
|
||||||
|
</ion-list>
|
||||||
|
</ion-content>
|
|
@ -0,0 +1,31 @@
|
||||||
|
// (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 { AddonCalendarSettingsPage } from './settings';
|
||||||
|
import { CorePipesModule } from '../../../../pipes/pipes.module';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
AddonCalendarSettingsPage,
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
CorePipesModule,
|
||||||
|
IonicPageModule.forChild(AddonCalendarSettingsPage),
|
||||||
|
TranslateModule.forChild()
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class AddonCalendarSettingsPageModule {}
|
|
@ -0,0 +1,3 @@
|
||||||
|
page-addon-calendar-settings {
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
// (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 } from '@angular/core';
|
||||||
|
import { IonicPage } from 'ionic-angular';
|
||||||
|
import { AddonCalendarProvider } from '../../providers/calendar';
|
||||||
|
import { CoreEventsProvider } from '../../../../providers/events';
|
||||||
|
import { CoreSitesProvider } from '../../../../providers/sites';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Page that displays the calendar settings.
|
||||||
|
*/
|
||||||
|
@IonicPage({segment: "addon-calendar-settings"})
|
||||||
|
@Component({
|
||||||
|
selector: 'page-addon-calendar-settings',
|
||||||
|
templateUrl: 'settings.html',
|
||||||
|
})
|
||||||
|
export class AddonCalendarSettingsPage {
|
||||||
|
|
||||||
|
defaultTime = 0;
|
||||||
|
|
||||||
|
constructor(private calendarProvider: AddonCalendarProvider, private eventsProvider: CoreEventsProvider,
|
||||||
|
private sitesProvider: CoreSitesProvider) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View loaded.
|
||||||
|
*/
|
||||||
|
ionViewDidLoad() {
|
||||||
|
this.calendarProvider.getDefaultNotificationTime().then((time) => {
|
||||||
|
this.defaultTime = time;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateDefaultTime(newTime) {
|
||||||
|
this.calendarProvider.setDefaultNotificationTime(newTime);
|
||||||
|
this.eventsProvider.trigger(AddonCalendarProvider.DEFAULT_NOTIFICATION_TIME_CHANGED, {time: newTime},
|
||||||
|
this.sitesProvider.getCurrentSiteId());
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,530 @@
|
||||||
|
// (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 { CoreLoggerProvider } from '../../../providers/logger';
|
||||||
|
import { CoreSitesProvider } from '../../../providers/sites';
|
||||||
|
import { CoreSite } from '../../../classes/site';
|
||||||
|
import { CoreCoursesProvider } from '../../../core/courses/providers/courses';
|
||||||
|
import { CoreTimeUtilsProvider } from '../../../providers/utils/time';
|
||||||
|
import { CoreGroupsProvider } from '../../../providers/groups';
|
||||||
|
import { CoreConstants } from '../../../core/constants';
|
||||||
|
import { CoreLocalNotificationsProvider } from '../../../providers/local-notifications';
|
||||||
|
import { CoreConfigProvider } from '../../../providers/config';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service to handle calendar events.
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class AddonCalendarProvider {
|
||||||
|
public static DAYS_INTERVAL = 30;
|
||||||
|
public static COMPONENT = 'AddonCalendarEvents';
|
||||||
|
public static DEFAULT_NOTIFICATION_TIME_CHANGED = 'AddonCalendarDefaultNotificationTimeChangedEvent';
|
||||||
|
protected DEFAULT_NOTIFICATION_TIME_SETTING = 'mmaCalendarDefaultNotifTime';
|
||||||
|
protected ROOT_CACHE_KEY = 'mmaCalendar:';
|
||||||
|
protected DEFAULT_NOTIFICATION_TIME = 60;
|
||||||
|
|
||||||
|
// Variables for database.
|
||||||
|
protected EVENTS_TABLE = 'calendar_events';
|
||||||
|
protected tablesSchema = [
|
||||||
|
{
|
||||||
|
name: this.EVENTS_TABLE,
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'id',
|
||||||
|
type: 'INTEGER',
|
||||||
|
primaryKey: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'notificationtime',
|
||||||
|
type: 'INTEGER'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'name',
|
||||||
|
type: 'TEXT',
|
||||||
|
notNull: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'description',
|
||||||
|
type: 'TEXT'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'eventtype',
|
||||||
|
type: 'TEXT'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'courseid',
|
||||||
|
type: 'INTEGER'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'timestart',
|
||||||
|
type: 'INTEGER'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'timeduration',
|
||||||
|
type: 'INTEGER'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'categoryid',
|
||||||
|
type: 'INTEGER'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'groupid',
|
||||||
|
type: 'INTEGER'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'instance',
|
||||||
|
type: 'INTEGER'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'modulename',
|
||||||
|
type: 'TEXT'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'timemodified',
|
||||||
|
type: 'INTEGER'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'repeatid',
|
||||||
|
type: 'INTEGER'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
protected logger;
|
||||||
|
|
||||||
|
constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private groupsProvider: CoreGroupsProvider,
|
||||||
|
private coursesProvider: CoreCoursesProvider, private timeUtils: CoreTimeUtilsProvider,
|
||||||
|
private localNotificationsProvider: CoreLocalNotificationsProvider, private configProvider: CoreConfigProvider) {
|
||||||
|
this.logger = logger.getInstance('AddonCalendarProvider');
|
||||||
|
this.sitesProvider.createTablesFromSchema(this.tablesSchema);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the configured default notification time.
|
||||||
|
*
|
||||||
|
* @param {string} [siteId] ID of the site. If not defined, use current site.
|
||||||
|
* @return {Promise<number>} Promise resolved with the default time.
|
||||||
|
*/
|
||||||
|
getDefaultNotificationTime(siteId?: string) : Promise<number> {
|
||||||
|
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||||
|
|
||||||
|
let key = this.DEFAULT_NOTIFICATION_TIME_SETTING + '#' + siteId;
|
||||||
|
return this.configProvider.get(key, this.DEFAULT_NOTIFICATION_TIME);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a calendar event. If the server request fails and data is not cached, try to get it from local DB.
|
||||||
|
*
|
||||||
|
* @param {number} id Event ID.
|
||||||
|
* @param {boolean} [refresh] True when we should update the event data.
|
||||||
|
* @param {string} [siteId] ID of the site. If not defined, use current site.
|
||||||
|
* @return {Promise<any>} Promise resolved when the event data is retrieved.
|
||||||
|
*/
|
||||||
|
getEvent(id: number, siteId?: string) : Promise<any> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
let presets = {
|
||||||
|
cacheKey: this.getEventCacheKey(id)
|
||||||
|
},
|
||||||
|
data = {
|
||||||
|
"options[userevents]": 0,
|
||||||
|
"options[siteevents]": 0,
|
||||||
|
"events[eventids][0]": id
|
||||||
|
};
|
||||||
|
return site.read('core_calendar_get_calendar_events', data, presets).then((response) => {
|
||||||
|
// The WebService returns all category events. Check the response to search for the event we want.
|
||||||
|
let event = response.events.find((e) => {return e.id == id});
|
||||||
|
return event || this.getEventFromLocalDb(id);
|
||||||
|
}).catch(() => {
|
||||||
|
return this.getEventFromLocalDb(id);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get cache key for a single event WS call.
|
||||||
|
*
|
||||||
|
* @param {number} id Event ID.
|
||||||
|
* @return {string} Cache key.
|
||||||
|
*/
|
||||||
|
protected getEventCacheKey(id: number): string {
|
||||||
|
return this.ROOT_CACHE_KEY + 'events:' + id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a calendar event from local Db.
|
||||||
|
*
|
||||||
|
* @param {number} id Event ID.
|
||||||
|
* @param {string} [siteId] ID of the site the event belongs to. If not defined, use current site.
|
||||||
|
* @return {Promise<any>} Promise resolved when the event data is retrieved.
|
||||||
|
*/
|
||||||
|
getEventFromLocalDb(id: number, siteId?: string) : Promise<any> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
return site.getDb().getRecord(this.EVENTS_TABLE, {id: id});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get event notification time. Always returns number of minutes (0 if disabled).
|
||||||
|
*
|
||||||
|
* @param {number} id Event ID.
|
||||||
|
* @param {string} [siteId] ID of the site the event belongs to. If not defined, use current site.
|
||||||
|
* @return {Promise<number>} Event notification time in minutes. 0 if disabled.
|
||||||
|
*/
|
||||||
|
getEventNotificationTime(id: number, siteId?: string) : Promise<number> {
|
||||||
|
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||||
|
|
||||||
|
return this.getEventNotificationTimeOption(id, siteId).then((time: number) => {
|
||||||
|
if (time == -1) {
|
||||||
|
return this.getDefaultNotificationTime(siteId);
|
||||||
|
}
|
||||||
|
return time;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get event notification time for options. Returns -1 for default time.
|
||||||
|
*
|
||||||
|
* @param {number} id Event ID.
|
||||||
|
* @param {string} [siteId] ID of the site the event belongs to. If not defined, use current site.
|
||||||
|
* @return {Promise<number>} Promise with wvent notification time in minutes. 0 if disabled, -1 if default time.
|
||||||
|
*/
|
||||||
|
getEventNotificationTimeOption(id: number, siteId?: string) : Promise<number> {
|
||||||
|
return this.getEventFromLocalDb(id, siteId).then((e) => {
|
||||||
|
return e.notificationtime || -1;
|
||||||
|
}).catch(() => {
|
||||||
|
return -1;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the events in a certain period. The period is calculated like this:
|
||||||
|
* start time: now + daysToStart
|
||||||
|
* end time: start time + daysInterval
|
||||||
|
* E.g. using provider.getEventsList(30, 30) is going to get the events starting after 30 days from now
|
||||||
|
* and ending before 60 days from now.
|
||||||
|
*
|
||||||
|
* @param {number} [daysToStart=0] Number of days from now to start getting events.
|
||||||
|
* @param {number} [daysInterval=30] Number of days between timestart and timeend.
|
||||||
|
* @param {string} [siteId] Site to get the events from. If not defined, use current site.
|
||||||
|
* @return {Promise<any[]>} Promise to be resolved when the participants are retrieved.
|
||||||
|
*/
|
||||||
|
getEventsList(daysToStart = 0, daysInterval = AddonCalendarProvider.DAYS_INTERVAL, siteId?: string) : Promise<any[]> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
siteId = site.getId();
|
||||||
|
|
||||||
|
return this.coursesProvider.getUserCourses(false, siteId).then((courses) => {
|
||||||
|
courses.push({id: site.getSiteHomeId()}); // Add front page.
|
||||||
|
|
||||||
|
return this.groupsProvider.getUserGroups(courses, siteId).then((groups) => {
|
||||||
|
let now = this.timeUtils.timestamp(),
|
||||||
|
start = now + (CoreConstants.secondsDay * daysToStart),
|
||||||
|
end = start + (CoreConstants.secondsDay * daysInterval);
|
||||||
|
|
||||||
|
// The core_calendar_get_calendar_events needs all the current user courses and groups.
|
||||||
|
let data = {
|
||||||
|
"options[userevents]": 1,
|
||||||
|
"options[siteevents]": 1,
|
||||||
|
"options[timestart]": start,
|
||||||
|
"options[timeend]": end
|
||||||
|
};
|
||||||
|
|
||||||
|
courses.forEach((course, index) => {
|
||||||
|
data["events[courseids][" + index + "]"] = course.id;
|
||||||
|
});
|
||||||
|
|
||||||
|
groups.forEach((group, index) => {
|
||||||
|
data["events[groupids][" + index + "]"] = group.id;
|
||||||
|
});
|
||||||
|
|
||||||
|
// We need to retrieve cached data using cache key because we have timestamp in the params.
|
||||||
|
let preSets = {
|
||||||
|
cacheKey: this.getEventsListCacheKey(daysToStart, daysInterval),
|
||||||
|
getCacheUsingCacheKey: true
|
||||||
|
};
|
||||||
|
|
||||||
|
return site.read('core_calendar_get_calendar_events', data, preSets).then((response) => {
|
||||||
|
this.storeEventsInLocalDB(response.events, siteId);
|
||||||
|
return response.events;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get prefix cache key for events list WS calls.
|
||||||
|
*
|
||||||
|
* @return {string} Prefix Cache key.
|
||||||
|
*/
|
||||||
|
protected getEventsListPrefixCacheKey() : string {
|
||||||
|
return this.ROOT_CACHE_KEY + 'eventslist:';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get cache key for events list WS calls.
|
||||||
|
*
|
||||||
|
* @param {number} daysToStart Number of days from now to start getting events.
|
||||||
|
* @param {number} daysInterval Number of days between timestart and timeend.
|
||||||
|
* @return {string} Cache key.
|
||||||
|
*/
|
||||||
|
protected getEventsListCacheKey(daysToStart: number, daysInterval: number) : string {
|
||||||
|
return this.getEventsListPrefixCacheKey() + daysToStart + ':' + daysInterval;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalidates events list and all the single events and related info.
|
||||||
|
*
|
||||||
|
* @param {any[]} courses List of courses or course ids.
|
||||||
|
* @param {string} [siteId] Site Id. If not defined, use current site.
|
||||||
|
* @return {Promise<any[]>} Promise resolved when the list is invalidated.
|
||||||
|
*/
|
||||||
|
invalidateEventsList(courses: any[], siteId?: string) : Promise<any[]> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
siteId = site.getId();
|
||||||
|
|
||||||
|
let promises = [];
|
||||||
|
|
||||||
|
promises.push(this.coursesProvider.invalidateUserCourses(siteId));
|
||||||
|
promises.push(this.groupsProvider.invalidateUserGroups(courses, siteId));
|
||||||
|
promises.push(site.invalidateWsCacheForKeyStartingWith(this.getEventsListPrefixCacheKey()));
|
||||||
|
return Promise.all(promises);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalidates a single event.
|
||||||
|
*
|
||||||
|
* @param {number} eventId List of courses or course ids.
|
||||||
|
* @param {string} [siteId] Site Id. If not defined, use current site.
|
||||||
|
* @return {Promise<any>} Promise resolved when the list is invalidated.
|
||||||
|
*/
|
||||||
|
invalidateEvent(eventId: number, siteId?: string) : Promise<any> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
return site.invalidateWsCacheForKey(this.getEventCacheKey(eventId));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if Calendar is disabled in a certain site.
|
||||||
|
*
|
||||||
|
* @param {CoreSite} [site] Site. If not defined, use current site.
|
||||||
|
* @return {boolean} Whether it's disabled.
|
||||||
|
*/
|
||||||
|
isCalendarDisabledInSite(site?: CoreSite) : boolean {
|
||||||
|
site = site || this.sitesProvider.getCurrentSite();
|
||||||
|
return site.isFeatureDisabled('$mmSideMenuDelegate_mmaCalendar');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if Calendar is disabled in a certain site.
|
||||||
|
*
|
||||||
|
* @param {string} [siteId] Site Id. If not defined, use current site.
|
||||||
|
* @return {Promise<boolean>} Promise resolved with true if disabled, rejected or resolved with false otherwise.
|
||||||
|
*/
|
||||||
|
isDisabled(siteId?: string) : Promise<boolean> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
return this.isCalendarDisabledInSite(site);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the next events for all the sites and schedules their notifications.
|
||||||
|
* If an event notification time is 0, cancel its scheduled notification (if any).
|
||||||
|
* If local notification plugin is not enabled, resolve the promise.
|
||||||
|
*
|
||||||
|
* @return {Promise} Promise resolved when all the notifications have been scheduled.
|
||||||
|
*/
|
||||||
|
scheduleAllSitesEventsNotifications() : Promise<any[]> {
|
||||||
|
if (this.localNotificationsProvider.isAvailable()) {
|
||||||
|
return this.sitesProvider.getSitesIds().then((siteIds) => {
|
||||||
|
let promises = [];
|
||||||
|
|
||||||
|
siteIds.forEach((siteId) => {
|
||||||
|
// Check if calendar is disabled for the site.
|
||||||
|
promises.push(this.isDisabled(siteId).then((disabled) => {
|
||||||
|
if (!disabled) {
|
||||||
|
// Get first events.
|
||||||
|
return this.getEventsList(undefined, undefined, siteId).then((events) => {
|
||||||
|
return this.scheduleEventsNotifications(events, siteId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
return Promise.all(promises);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return Promise.resolve([]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Schedules an event notification. If time is 0, cancel scheduled notification if any.
|
||||||
|
* If local notification plugin is not enabled, resolve the promise.
|
||||||
|
*
|
||||||
|
* @param {any} event Event to schedule.
|
||||||
|
* @param {number} time Notification setting time (in minutes). E.g. 10 means "notificate 10 minutes before start".
|
||||||
|
* @param {string} [siteId] Site ID the event belongs to. If not defined, use current site.
|
||||||
|
* @return {Promise<void>} Promise resolved when the notification is scheduled.
|
||||||
|
*/
|
||||||
|
scheduleEventNotification(event: any, time: number, siteId?: string) : Promise<void> {
|
||||||
|
if (this.localNotificationsProvider.isAvailable()) {
|
||||||
|
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||||
|
|
||||||
|
if (time === 0) {
|
||||||
|
// Cancel if it was scheduled.
|
||||||
|
return this.localNotificationsProvider.cancel(event.id, AddonCalendarProvider.COMPONENT, siteId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If time is -1, get event default time.
|
||||||
|
let promise = time == -1 ? this.getDefaultNotificationTime(siteId) : Promise.resolve(time);
|
||||||
|
|
||||||
|
return promise.then((time) => {
|
||||||
|
let timeEnd = (event.timestart + event.timeduration) * 1000;
|
||||||
|
if (timeEnd <= new Date().getTime()) {
|
||||||
|
// The event has finished already, don't schedule it.
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
let dateTriggered = new Date((event.timestart - (time * 60)) * 1000),
|
||||||
|
startDate = new Date(event.timestart * 1000),
|
||||||
|
notification = {
|
||||||
|
id: event.id,
|
||||||
|
title: event.name,
|
||||||
|
text: startDate.toLocaleString(),
|
||||||
|
at: dateTriggered,
|
||||||
|
data: {
|
||||||
|
eventid: event.id,
|
||||||
|
siteid: siteId
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.localNotificationsProvider.schedule(notification, AddonCalendarProvider.COMPONENT, siteId);
|
||||||
|
});
|
||||||
|
|
||||||
|
} else {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Schedules the notifications for a list of events.
|
||||||
|
* If an event notification time is 0, cancel its scheduled notification (if any).
|
||||||
|
* If local notification plugin is not enabled, resolve the promise.
|
||||||
|
*
|
||||||
|
* @param {any[]} events Events to schedule.
|
||||||
|
* @param {string} [siteId] ID of the site the events belong to. If not defined, use current site.
|
||||||
|
* @return {Promise<any[]>} Promise resolved when all the notifications have been scheduled.
|
||||||
|
*/
|
||||||
|
scheduleEventsNotifications(events: any[], siteId?: string) : Promise<any[]> {
|
||||||
|
var promises = [];
|
||||||
|
|
||||||
|
if (this.localNotificationsProvider.isAvailable()) {
|
||||||
|
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||||
|
events.forEach((e) => {
|
||||||
|
promises.push(this.getEventNotificationTime(e.id, siteId).then((time) => {
|
||||||
|
return this.scheduleEventNotification(e, time, siteId);
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.all(promises);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the default notification time.
|
||||||
|
*
|
||||||
|
* @param {number} time New default time.
|
||||||
|
* @param {string} [siteId] ID of the site. If not defined, use current site.
|
||||||
|
* @return {Promise<any[]>} Promise resolved when stored.
|
||||||
|
*/
|
||||||
|
setDefaultNotificationTime(time: number, siteId?: string) : Promise<any[]> {
|
||||||
|
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||||
|
|
||||||
|
let key = this.DEFAULT_NOTIFICATION_TIME_SETTING + '#' + siteId;
|
||||||
|
return this.configProvider.set(key, time);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store events in local DB.
|
||||||
|
*
|
||||||
|
* @param {any[]} events Events to store.
|
||||||
|
* @param {string} [siteId] ID of the site the event belongs to. If not defined, use current site.
|
||||||
|
* @return {Promise<any[]>} Promise resolved when the events are stored.
|
||||||
|
*/
|
||||||
|
protected storeEventsInLocalDB(events: any[], siteId?: string) : Promise<any[]> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
siteId = site.getId();
|
||||||
|
|
||||||
|
let promises = [],
|
||||||
|
db = site.getDb();
|
||||||
|
|
||||||
|
events.forEach((event) => {
|
||||||
|
// Don't override event notification time if the user configured it.
|
||||||
|
promises.push(this.getEventFromLocalDb(event.id, siteId).catch(() => {
|
||||||
|
// Event not stored, return empty object.
|
||||||
|
return {};
|
||||||
|
}).then((e) => {
|
||||||
|
let eventRecord = {
|
||||||
|
id: event.id,
|
||||||
|
name: event.name,
|
||||||
|
description: event.description,
|
||||||
|
eventtype: event.eventtype,
|
||||||
|
courseid: event.courseid,
|
||||||
|
timestart: event.timestart,
|
||||||
|
timeduration: event.timeduration,
|
||||||
|
categoryid: event.categoryid,
|
||||||
|
groupid: event.groupid,
|
||||||
|
instance: event.instance,
|
||||||
|
modulename: event.modulename,
|
||||||
|
timemodified: event.timemodified,
|
||||||
|
repeatid: event.repeatid,
|
||||||
|
notificationtime: e.notificationtime || -1
|
||||||
|
};
|
||||||
|
|
||||||
|
return db.insertOrUpdateRecord(this.EVENTS_TABLE, eventRecord, {id: eventRecord.id});
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
return Promise.all(promises);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates an event notification time and schedule a new notification.
|
||||||
|
*
|
||||||
|
* @param {any} event Event to update its notification time.
|
||||||
|
* @param {number} time New notification setting time (in minutes). E.g. 10 means "notificate 10 minutes before start".
|
||||||
|
* @param {string} [siteId] ID of the site the event belongs to. If not defined, use current site.
|
||||||
|
* @return {Promise<void>} Promise resolved when the notification is updated.
|
||||||
|
*/
|
||||||
|
updateNotificationTime(event: any, time: number, siteId?: string) : Promise<void> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
if (!this.sitesProvider.isLoggedIn()) {
|
||||||
|
// Not logged in, we can't get the site DB. User logged out or session expired while an operation was ongoing.
|
||||||
|
return Promise.reject(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
event.notificationtime = time;
|
||||||
|
|
||||||
|
return site.getDb().insertOrUpdateRecord(this.EVENTS_TABLE, event, {id: event.id}).then(() => {
|
||||||
|
return this.scheduleEventNotification(event, time);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
// (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 { AddonCalendarProvider } from './calendar';
|
||||||
|
import { CoreMainMenuHandler, CoreMainMenuHandlerData } from '../../../core/mainmenu/providers/delegate';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler to inject an option into main menu.
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class AddonCalendarMainMenuHandler implements CoreMainMenuHandler {
|
||||||
|
name = 'AddonCalendar';
|
||||||
|
priority = 400;
|
||||||
|
|
||||||
|
constructor(private calendarProvider: AddonCalendarProvider) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the handler is enabled on a site level.
|
||||||
|
*
|
||||||
|
* @return {boolean} Whether or not the handler is enabled on a site level.
|
||||||
|
*/
|
||||||
|
isEnabled(): boolean|Promise<boolean> {
|
||||||
|
let isDisabled = this.calendarProvider.isCalendarDisabledInSite();
|
||||||
|
return !isDisabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the data needed to render the handler.
|
||||||
|
*
|
||||||
|
* @return {CoreMainMenuHandlerData} Data needed to render the handler.
|
||||||
|
*/
|
||||||
|
getDisplayData(): CoreMainMenuHandlerData {
|
||||||
|
return {
|
||||||
|
icon: 'calendar',
|
||||||
|
title: 'addon.calendar.calendar',
|
||||||
|
page: 'AddonCalendarListPage',
|
||||||
|
class: 'addon-calendar-handler'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
// (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 { CoreLoggerProvider } from '../../../providers/logger';
|
||||||
|
import { CoreSitesProvider } from '../../../providers/sites';
|
||||||
|
//import { CoreCourseProvider } from '../../../core/course/providers/course';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service that provides some features regarding lists of courses and categories.
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class AddonCalendarHelperProvider {
|
||||||
|
protected logger;
|
||||||
|
|
||||||
|
private EVENTICONS = {
|
||||||
|
'course': 'ionic',
|
||||||
|
'group': 'people',
|
||||||
|
'site': 'globe',
|
||||||
|
'user': 'person',
|
||||||
|
'category': 'albums'
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider/*, private courseProvider: CoreCourseProvider*/) {
|
||||||
|
this.logger = logger.getInstance('AddonCalendarHelperProvider');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience function to format some event data to be rendered.
|
||||||
|
*
|
||||||
|
* @param {any} e Event to format.
|
||||||
|
*/
|
||||||
|
formatEventData(e: any) {
|
||||||
|
e.icon = this.EVENTICONS[e.eventtype] || false;
|
||||||
|
if (!e.icon) {
|
||||||
|
// @todo: It's a module event.
|
||||||
|
//e.icon = this.courseProvider.getModuleIconSrc(e.modulename);
|
||||||
|
e.moduleIcon = e.icon;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
|
@ -24,6 +24,9 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bar-buttons core-context-menu .button-clear-ios {
|
||||||
|
color: $toolbar-ios-button-color;
|
||||||
|
}
|
||||||
|
|
||||||
// Highlights inside the input element.
|
// Highlights inside the input element.
|
||||||
@if ($core-text-input-ios-show-highlight) {
|
@if ($core-text-input-ios-show-highlight) {
|
||||||
|
@ -87,4 +90,4 @@
|
||||||
@include ios-input-highlight($text-input-ios-highlight-color-invalid);
|
@include ios-input-highlight($text-input-ios-highlight-color-invalid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,10 @@
|
||||||
height: calc(100% - #{($card-md-margin-end + $card-md-margin-start)});
|
height: calc(100% - #{($card-md-margin-end + $card-md-margin-start)});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bar-buttons core-context-menu .button-clear-md {
|
||||||
|
color: $toolbar-md-button-color;
|
||||||
|
}
|
||||||
|
|
||||||
// Highlights inside the input element.
|
// Highlights inside the input element.
|
||||||
@if ($core-text-input-md-show-highlight) {
|
@if ($core-text-input-md-show-highlight) {
|
||||||
.card-md, .list-md {
|
.card-md, .list-md {
|
||||||
|
@ -75,4 +79,4 @@
|
||||||
@include md-input-highlight($text-input-md-highlight-color-invalid);
|
@include md-input-highlight($text-input-md-highlight-color-invalid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,7 +55,7 @@ import { CoreMainMenuModule } from '../core/mainmenu/mainmenu.module';
|
||||||
import { CoreCoursesModule } from '../core/courses/courses.module';
|
import { CoreCoursesModule } from '../core/courses/courses.module';
|
||||||
import { CoreFileUploaderModule } from '../core/fileuploader/fileuploader.module';
|
import { CoreFileUploaderModule } from '../core/fileuploader/fileuploader.module';
|
||||||
import { CoreSharedFilesModule } from '../core/sharedfiles/sharedfiles.module';
|
import { CoreSharedFilesModule } from '../core/sharedfiles/sharedfiles.module';
|
||||||
|
import { AddonCalendarModule } from '../addon/calendar/calendar.module';
|
||||||
|
|
||||||
// For translate loader. AoT requires an exported function for factories.
|
// For translate loader. AoT requires an exported function for factories.
|
||||||
export function createTranslateLoader(http: HttpClient) {
|
export function createTranslateLoader(http: HttpClient) {
|
||||||
|
@ -86,7 +86,8 @@ export function createTranslateLoader(http: HttpClient) {
|
||||||
CoreCoursesModule,
|
CoreCoursesModule,
|
||||||
CoreFileUploaderModule,
|
CoreFileUploaderModule,
|
||||||
CoreSharedFilesModule,
|
CoreSharedFilesModule,
|
||||||
CoreComponentsModule
|
CoreComponentsModule,
|
||||||
|
AddonCalendarModule
|
||||||
],
|
],
|
||||||
bootstrap: [IonicApp],
|
bootstrap: [IonicApp],
|
||||||
entryComponents: [
|
entryComponents: [
|
||||||
|
|
|
@ -11,3 +11,7 @@
|
||||||
.col[align-self-stretch] .card-wp {
|
.col[align-self-stretch] .card-wp {
|
||||||
height: calc(100% - #{($card-wp-margin-end + $card-wp-margin-start)});
|
height: calc(100% - #{($card-wp-margin-end + $card-wp-margin-start)});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bar-buttons core-context-menu .button-clear-wp {
|
||||||
|
color: $toolbar-wp-button-color;
|
||||||
|
}
|
||||||
|
|
|
@ -504,27 +504,27 @@ export class CoreSite {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Session expired, trigger event.
|
// Session expired, trigger event.
|
||||||
this.eventsProvider.trigger(CoreEventsProvider.SESSION_EXPIRED, {siteId: this.id});
|
this.eventsProvider.trigger(CoreEventsProvider.SESSION_EXPIRED, {}, this.id);
|
||||||
// Change error message. We'll try to get data from cache.
|
// Change error message. We'll try to get data from cache.
|
||||||
error.message = this.translate.instant('core.lostconnection');
|
error.message = this.translate.instant('core.lostconnection');
|
||||||
} else if (error.errorcode === 'userdeleted') {
|
} else if (error.errorcode === 'userdeleted') {
|
||||||
// User deleted, trigger event.
|
// User deleted, trigger event.
|
||||||
this.eventsProvider.trigger(CoreEventsProvider.USER_DELETED, {siteId: this.id, params: data});
|
this.eventsProvider.trigger(CoreEventsProvider.USER_DELETED, {params: data}, this.id);
|
||||||
error.message = this.translate.instant('core.userdeleted');
|
error.message = this.translate.instant('core.userdeleted');
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
} else if (error.errorcode === 'forcepasswordchangenotice') {
|
} else if (error.errorcode === 'forcepasswordchangenotice') {
|
||||||
// Password Change Forced, trigger event.
|
// Password Change Forced, trigger event.
|
||||||
this.eventsProvider.trigger(CoreEventsProvider.PASSWORD_CHANGE_FORCED, {siteId: this.id});
|
this.eventsProvider.trigger(CoreEventsProvider.PASSWORD_CHANGE_FORCED, {}, this.id);
|
||||||
error.message = this.translate.instant('core.forcepasswordchangenotice');
|
error.message = this.translate.instant('core.forcepasswordchangenotice');
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
} else if (error.errorcode === 'usernotfullysetup') {
|
} else if (error.errorcode === 'usernotfullysetup') {
|
||||||
// User not fully setup, trigger event.
|
// User not fully setup, trigger event.
|
||||||
this.eventsProvider.trigger(CoreEventsProvider.USER_NOT_FULLY_SETUP, {siteId: this.id});
|
this.eventsProvider.trigger(CoreEventsProvider.USER_NOT_FULLY_SETUP, {}, this.id);
|
||||||
error.message = this.translate.instant('core.usernotfullysetup');
|
error.message = this.translate.instant('core.usernotfullysetup');
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
} else if (error.errorcode === 'sitepolicynotagreed') {
|
} else if (error.errorcode === 'sitepolicynotagreed') {
|
||||||
// Site policy not agreed, trigger event.
|
// Site policy not agreed, trigger event.
|
||||||
this.eventsProvider.trigger(CoreEventsProvider.SITE_POLICY_NOT_AGREED, {siteId: this.id});
|
this.eventsProvider.trigger(CoreEventsProvider.SITE_POLICY_NOT_AGREED, {}, this.id);
|
||||||
error.message = this.translate.instant('core.sitepolicynotagreederror');
|
error.message = this.translate.instant('core.sitepolicynotagreederror');
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
} else if (error.errorcode === 'dmlwriteexception' && this.textUtils.hasUnicodeData(data)) {
|
} else if (error.errorcode === 'dmlwriteexception' && this.textUtils.hasUnicodeData(data)) {
|
||||||
|
@ -802,8 +802,8 @@ export class CoreSite {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.logger.debug('Invalidate cache for key starting with: ' + key);
|
this.logger.debug('Invalidate cache for key starting with: ' + key);
|
||||||
let sql = 'UPDATE ' + this.WS_CACHE_TABLE + ' SET expirationTime=0 WHERE key LIKE ?%';
|
let sql = 'UPDATE ' + this.WS_CACHE_TABLE + ' SET expirationTime=0 WHERE key LIKE ?';
|
||||||
return this.db.execute(sql, [key]);
|
return this.db.execute(sql, [key + "%"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -21,6 +21,7 @@ import { CoreLoadingComponent } from './loading/loading';
|
||||||
import { CoreMarkRequiredComponent } from './mark-required/mark-required';
|
import { CoreMarkRequiredComponent } from './mark-required/mark-required';
|
||||||
import { CoreInputErrorsComponent } from './input-errors/input-errors';
|
import { CoreInputErrorsComponent } from './input-errors/input-errors';
|
||||||
import { CoreShowPasswordComponent } from './show-password/show-password';
|
import { CoreShowPasswordComponent } from './show-password/show-password';
|
||||||
|
import { CoreSplitViewComponent } from './split-view/split-view';
|
||||||
import { CoreIframeComponent } from './iframe/iframe';
|
import { CoreIframeComponent } from './iframe/iframe';
|
||||||
import { CoreProgressBarComponent } from './progress-bar/progress-bar';
|
import { CoreProgressBarComponent } from './progress-bar/progress-bar';
|
||||||
import { CoreEmptyBoxComponent } from './empty-box/empty-box';
|
import { CoreEmptyBoxComponent } from './empty-box/empty-box';
|
||||||
|
@ -29,6 +30,7 @@ import { CoreFileComponent } from './file/file';
|
||||||
import { CoreContextMenuComponent } from './context-menu/context-menu';
|
import { CoreContextMenuComponent } from './context-menu/context-menu';
|
||||||
import { CoreContextMenuItemComponent } from './context-menu/context-menu-item';
|
import { CoreContextMenuItemComponent } from './context-menu/context-menu-item';
|
||||||
import { CoreContextMenuPopoverComponent } from './context-menu/context-menu-popover';
|
import { CoreContextMenuPopoverComponent } from './context-menu/context-menu-popover';
|
||||||
|
import { CoreCoursePickerMenuPopoverComponent } from './course-picker-menu/course-picker-menu-popover';
|
||||||
import { CoreChronoComponent } from './chrono/chrono';
|
import { CoreChronoComponent } from './chrono/chrono';
|
||||||
import { CoreLocalFileComponent } from './local-file/local-file';
|
import { CoreLocalFileComponent } from './local-file/local-file';
|
||||||
import { CoreSitePickerComponent } from './site-picker/site-picker';
|
import { CoreSitePickerComponent } from './site-picker/site-picker';
|
||||||
|
@ -39,6 +41,7 @@ import { CoreSitePickerComponent } from './site-picker/site-picker';
|
||||||
CoreMarkRequiredComponent,
|
CoreMarkRequiredComponent,
|
||||||
CoreInputErrorsComponent,
|
CoreInputErrorsComponent,
|
||||||
CoreShowPasswordComponent,
|
CoreShowPasswordComponent,
|
||||||
|
CoreSplitViewComponent,
|
||||||
CoreIframeComponent,
|
CoreIframeComponent,
|
||||||
CoreProgressBarComponent,
|
CoreProgressBarComponent,
|
||||||
CoreEmptyBoxComponent,
|
CoreEmptyBoxComponent,
|
||||||
|
@ -47,12 +50,14 @@ import { CoreSitePickerComponent } from './site-picker/site-picker';
|
||||||
CoreContextMenuComponent,
|
CoreContextMenuComponent,
|
||||||
CoreContextMenuItemComponent,
|
CoreContextMenuItemComponent,
|
||||||
CoreContextMenuPopoverComponent,
|
CoreContextMenuPopoverComponent,
|
||||||
|
CoreCoursePickerMenuPopoverComponent,
|
||||||
CoreChronoComponent,
|
CoreChronoComponent,
|
||||||
CoreLocalFileComponent,
|
CoreLocalFileComponent,
|
||||||
CoreSitePickerComponent
|
CoreSitePickerComponent
|
||||||
],
|
],
|
||||||
entryComponents: [
|
entryComponents: [
|
||||||
CoreContextMenuPopoverComponent
|
CoreContextMenuPopoverComponent,
|
||||||
|
CoreCoursePickerMenuPopoverComponent
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
IonicModule,
|
IonicModule,
|
||||||
|
@ -65,6 +70,7 @@ import { CoreSitePickerComponent } from './site-picker/site-picker';
|
||||||
CoreMarkRequiredComponent,
|
CoreMarkRequiredComponent,
|
||||||
CoreInputErrorsComponent,
|
CoreInputErrorsComponent,
|
||||||
CoreShowPasswordComponent,
|
CoreShowPasswordComponent,
|
||||||
|
CoreSplitViewComponent,
|
||||||
CoreIframeComponent,
|
CoreIframeComponent,
|
||||||
CoreProgressBarComponent,
|
CoreProgressBarComponent,
|
||||||
CoreEmptyBoxComponent,
|
CoreEmptyBoxComponent,
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
<ion-list radio-group [(ngModel)]="courseId">
|
||||||
|
<ion-item text-wrap *ngFor="let course of courses" >
|
||||||
|
<ion-label><core-format-text [text]="course.fullname"></core-format-text></ion-label>
|
||||||
|
<ion-radio value="{{course.id}}" (ionSelect)="coursePicked($event, course)" ></ion-radio>
|
||||||
|
</ion-item>
|
||||||
|
</ion-list>
|
|
@ -0,0 +1,45 @@
|
||||||
|
// (C) Copyright 2015 Martin Dougiamas
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { NavParams, ViewController } from 'ionic-angular';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component to display a list of courses.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'core-course-picker-menu-popover',
|
||||||
|
templateUrl: 'course-picker-menu-popover.html'
|
||||||
|
})
|
||||||
|
export class CoreCoursePickerMenuPopoverComponent {
|
||||||
|
courses: any[];
|
||||||
|
courseId = -1;
|
||||||
|
|
||||||
|
constructor(private navParams: NavParams, private viewCtrl: ViewController) {
|
||||||
|
this.courses = navParams.get('courses') || [];
|
||||||
|
this.courseId = navParams.get('courseId') || -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function called when a course is clicked.
|
||||||
|
*
|
||||||
|
* @param {Event} event Click event.
|
||||||
|
* @param {any} course Course object clicked.
|
||||||
|
* @return {boolean} Return true if success, false if error.
|
||||||
|
*/
|
||||||
|
coursePicked(event: Event, course: any) : boolean {
|
||||||
|
this.viewCtrl.dismiss(course);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
<ion-header>
|
||||||
|
<ion-navbar>
|
||||||
|
<ion-title> </ion-title>
|
||||||
|
</ion-navbar>
|
||||||
|
</ion-header>
|
||||||
|
|
||||||
|
<ion-content>
|
||||||
|
<core-empty-box icon="arrow-dropleft" [message]="'core.emptysplit' | translate"></core-empty-box>
|
||||||
|
</ion-content>
|
|
@ -0,0 +1,36 @@
|
||||||
|
// (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.
|
||||||
|
|
||||||
|
// Code based on https://github.com/martinpritchardelevate/ionic-split-pane-demo
|
||||||
|
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { IonicPageModule } from 'ionic-angular';
|
||||||
|
import { CoreSplitViewPlaceholderPage } from './placeholder';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { CoreComponentsModule } from '../../components.module';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
CoreSplitViewPlaceholderPage,
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
CoreComponentsModule,
|
||||||
|
IonicPageModule.forChild(CoreSplitViewPlaceholderPage),
|
||||||
|
TranslateModule.forChild()
|
||||||
|
],
|
||||||
|
exports: [
|
||||||
|
CoreSplitViewPlaceholderPage
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class CorePlaceholderPageModule { }
|
|
@ -0,0 +1,3 @@
|
||||||
|
core-placeholder {
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
// (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.
|
||||||
|
|
||||||
|
// Code based on https://github.com/martinpritchardelevate/ionic-split-pane-demo
|
||||||
|
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { IonicPage } from 'ionic-angular';
|
||||||
|
|
||||||
|
@IonicPage({segment: "core-placeholder"})
|
||||||
|
@Component({
|
||||||
|
selector: 'core-placeholder',
|
||||||
|
templateUrl: 'placeholder.html',
|
||||||
|
})
|
||||||
|
export class CoreSplitViewPlaceholderPage {
|
||||||
|
|
||||||
|
constructor() { }
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
<ion-split-pane (ionChange)="onSplitPaneChanged($event._visible);" [when]="when">
|
||||||
|
<ion-menu [content]="detailNav" type="push">
|
||||||
|
<ion-header><ion-toolbar><ion-title></ion-title></ion-toolbar></ion-header>
|
||||||
|
<ng-content></ng-content>
|
||||||
|
</ion-menu>
|
||||||
|
<ion-nav [root]="detailPage" #detailNav main></ion-nav>
|
||||||
|
</ion-split-pane>
|
|
@ -0,0 +1,39 @@
|
||||||
|
core-split-view {
|
||||||
|
ion-menu.split-pane-side {
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
.menu-inner {
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
-webkit-transform: initial;
|
||||||
|
transform: initial;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.split-pane-main {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.split-pane-visible {
|
||||||
|
.split-pane-main {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.split-pane-side .core-split-item-selected {
|
||||||
|
background-color: $gray-lighter;
|
||||||
|
border-left: 5px solid $core-color-light;
|
||||||
|
&.item-md {
|
||||||
|
padding-left: $item-md-padding-start - 5px;
|
||||||
|
}
|
||||||
|
&.item-ios {
|
||||||
|
padding-left: $item-ios-padding-start - 5px;
|
||||||
|
}
|
||||||
|
&.item-wp {
|
||||||
|
padding-left: $item-wp-padding-start - 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,147 @@
|
||||||
|
// (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.
|
||||||
|
|
||||||
|
// Code based on https://github.com/martinpritchardelevate/ionic-split-pane-demo
|
||||||
|
|
||||||
|
import { Component, ViewChild, Injectable, Input, ElementRef, OnInit } from '@angular/core';
|
||||||
|
import { NavController, Nav } from 'ionic-angular';
|
||||||
|
import { CoreSplitViewPlaceholderPage } from './placeholder/placeholder';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Directive to create a split view layout.
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
* To init/change the right pane contents (content pane), inject this component in the master page.
|
||||||
|
* @ViewChild(CoreSplitViewComponent) splitviewCtrl: CoreSplitViewComponent;
|
||||||
|
* Then use the push function to load.
|
||||||
|
*
|
||||||
|
* Accepts the following params:
|
||||||
|
*
|
||||||
|
* @param {string|boolean} [when] When the split-pane should be shown. Can be a CSS media query expression, or a shortcut
|
||||||
|
* expression. Can also be a boolean expression. Check split-pane component documentation for more information.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
*
|
||||||
|
* <core-split-view [when]="lg">
|
||||||
|
* <ion-content><!-- CONTENT TO SHOW ON THE LEFT PANEL (MENU) --></ion-content>
|
||||||
|
* </core-split-view>
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'core-split-view',
|
||||||
|
templateUrl: 'split-view.html'
|
||||||
|
})
|
||||||
|
export class CoreSplitViewComponent implements OnInit {
|
||||||
|
// @todo Mix both panels header buttons
|
||||||
|
|
||||||
|
@ViewChild('detailNav') detailNav: Nav;
|
||||||
|
@Input() when?: string | boolean = "md"; //
|
||||||
|
protected isEnabled: boolean = false;
|
||||||
|
protected masterPageName: string = "";
|
||||||
|
protected loadDetailPage: any = false;
|
||||||
|
protected element: HTMLElement; // Current element.
|
||||||
|
|
||||||
|
// Empty placeholder for the 'detail' page.
|
||||||
|
detailPage: any = null;
|
||||||
|
|
||||||
|
constructor(private masterNav: NavController, element: ElementRef) {
|
||||||
|
this.element = element.nativeElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component being initialized.
|
||||||
|
*/
|
||||||
|
ngOnInit() {
|
||||||
|
// Get the master page name and set an empty page as a placeholder.
|
||||||
|
this.masterPageName = this.masterNav.getActive().component.name;
|
||||||
|
this.emptyDetails();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if both panels are shown. It depends on screen width.
|
||||||
|
*
|
||||||
|
* @return {boolean} If split view is enabled.
|
||||||
|
*/
|
||||||
|
isOn(): boolean {
|
||||||
|
return this.isEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Push a page to the navigation stack. It will decide where to load it depending on the size of the screen.
|
||||||
|
*
|
||||||
|
* @param {any} page The component class or deeplink name you want to push onto the navigation stack.
|
||||||
|
* @param {any} params Any NavParams you want to pass along to the next view.
|
||||||
|
*/
|
||||||
|
push(page: any, params?: any, element?: HTMLElement) {
|
||||||
|
if (this.isEnabled) {
|
||||||
|
this.detailNav.setRoot(page, params);
|
||||||
|
} else {
|
||||||
|
this.loadDetailPage = {
|
||||||
|
component: page,
|
||||||
|
data: params
|
||||||
|
};
|
||||||
|
this.masterNav.push(page, params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the details panel to default info.
|
||||||
|
*/
|
||||||
|
emptyDetails() {
|
||||||
|
this.loadDetailPage = false;
|
||||||
|
this.detailNav.setRoot('CoreSplitViewPlaceholderPage');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Splitpanel visibility has changed.
|
||||||
|
*
|
||||||
|
* @param {Boolean} isOn If it fits both panels at the same time.
|
||||||
|
*/
|
||||||
|
onSplitPaneChanged(isOn) {
|
||||||
|
this.isEnabled = isOn;
|
||||||
|
if (this.masterNav && this.detailNav) {
|
||||||
|
(isOn) ? this.activateSplitView() : this.deactivateSplitView();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable the split view, show both panels and do some magical navigation.
|
||||||
|
*/
|
||||||
|
activateSplitView() {
|
||||||
|
let currentView = this.masterNav.getActive(),
|
||||||
|
currentPageName = currentView.component.name;
|
||||||
|
if (currentPageName != this.masterPageName) {
|
||||||
|
// CurrentView is a 'Detail' page remove it from the 'master' nav stack.
|
||||||
|
this.masterNav.pop();
|
||||||
|
|
||||||
|
// and add it to the 'detail' nav stack.
|
||||||
|
this.detailNav.setRoot(currentView.component, currentView.data);
|
||||||
|
} else if (this.loadDetailPage) {
|
||||||
|
// MasterPage is shown, load the last detail page if found.
|
||||||
|
this.detailNav.setRoot(this.loadDetailPage.component, this.loadDetailPage.data);
|
||||||
|
}
|
||||||
|
this.loadDetailPage = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disabled the split view, show only one panel and do some magical navigation.
|
||||||
|
*/
|
||||||
|
deactivateSplitView() {
|
||||||
|
let detailView = this.detailNav.getActive(),
|
||||||
|
currentPageName = detailView.component.name;
|
||||||
|
if (currentPageName != 'CoreSplitViewPlaceholderPage') {
|
||||||
|
// Current detail view is a 'Detail' page so, not the placeholder page, push it on 'master' nav stack.
|
||||||
|
this.masterNav.push(detailView.component, detailView.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -21,7 +21,7 @@ import { CoreCoursesProvider } from '../../providers/courses';
|
||||||
/**
|
/**
|
||||||
* Page that displays available courses in current site.
|
* Page that displays available courses in current site.
|
||||||
*/
|
*/
|
||||||
@IonicPage()
|
@IonicPage({segment: "core-courses-available-courses"})
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'page-core-courses-available-courses',
|
selector: 'page-core-courses-available-courses',
|
||||||
templateUrl: 'available-courses.html',
|
templateUrl: 'available-courses.html',
|
||||||
|
|
|
@ -23,7 +23,7 @@ import { CoreCoursesProvider } from '../../providers/courses';
|
||||||
/**
|
/**
|
||||||
* Page that displays a list of categories and the courses in the current category if any.
|
* Page that displays a list of categories and the courses in the current category if any.
|
||||||
*/
|
*/
|
||||||
@IonicPage()
|
@IonicPage({segment: "core-courses-categories"})
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'page-core-courses-categories',
|
selector: 'page-core-courses-categories',
|
||||||
templateUrl: 'categories.html',
|
templateUrl: 'categories.html',
|
||||||
|
|
|
@ -26,7 +26,7 @@ import { CoreCoursesDelegate } from '../../providers/delegate';
|
||||||
/**
|
/**
|
||||||
* Page that allows "previewing" a course and enrolling in it if enabled and not enrolled.
|
* Page that allows "previewing" a course and enrolling in it if enabled and not enrolled.
|
||||||
*/
|
*/
|
||||||
@IonicPage()
|
@IonicPage({segment: "core-courses-course-preview"})
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'page-core-courses-course-preview',
|
selector: 'page-core-courses-course-preview',
|
||||||
templateUrl: 'course-preview.html',
|
templateUrl: 'course-preview.html',
|
||||||
|
@ -307,7 +307,7 @@ export class CoreCoursesCoursePreviewPage implements OnDestroy {
|
||||||
this.refreshData().finally(() => {
|
this.refreshData().finally(() => {
|
||||||
// My courses have been updated, trigger event.
|
// My courses have been updated, trigger event.
|
||||||
this.eventsProvider.trigger(
|
this.eventsProvider.trigger(
|
||||||
CoreCoursesProvider.EVENT_MY_COURSES_UPDATED, {siteId: this.sitesProvider.getCurrentSiteId()});
|
CoreCoursesProvider.EVENT_MY_COURSES_UPDATED, {}, this.sitesProvider.getCurrentSiteId());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
|
|
|
@ -22,7 +22,7 @@ import { CoreCoursesProvider } from '../../providers/courses';
|
||||||
/**
|
/**
|
||||||
* Page that displays the list of courses the user is enrolled in.
|
* Page that displays the list of courses the user is enrolled in.
|
||||||
*/
|
*/
|
||||||
@IonicPage()
|
@IonicPage({segment: "core-courses-my-courses"})
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'page-core-courses-my-courses',
|
selector: 'page-core-courses-my-courses',
|
||||||
templateUrl: 'my-courses.html',
|
templateUrl: 'my-courses.html',
|
||||||
|
@ -53,17 +53,13 @@ export class CoreCoursesMyCoursesPage implements OnDestroy {
|
||||||
this.coursesLoaded = true;
|
this.coursesLoaded = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.myCoursesObserver = this.eventsProvider.on(CoreCoursesProvider.EVENT_MY_COURSES_UPDATED, (data) => {
|
this.myCoursesObserver = this.eventsProvider.on(CoreCoursesProvider.EVENT_MY_COURSES_UPDATED, () => {
|
||||||
if (data.siteId == this.sitesProvider.getCurrentSiteId()) {
|
this.fetchCourses();
|
||||||
this.fetchCourses();
|
}, this.sitesProvider.getCurrentSiteId());
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.siteUpdatedObserver = this.eventsProvider.on(CoreEventsProvider.SITE_UPDATED, (data) => {
|
this.siteUpdatedObserver = this.eventsProvider.on(CoreEventsProvider.SITE_UPDATED, () => {
|
||||||
if (data.siteId == this.sitesProvider.getCurrentSiteId()) {
|
this.searchEnabled = !this.coursesProvider.isSearchCoursesDisabledInSite();
|
||||||
this.searchEnabled = !this.coursesProvider.isSearchCoursesDisabledInSite();
|
}, this.sitesProvider.getCurrentSiteId());
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -22,7 +22,7 @@ import * as moment from 'moment';
|
||||||
/**
|
/**
|
||||||
* Page that displays My Overview.
|
* Page that displays My Overview.
|
||||||
*/
|
*/
|
||||||
@IonicPage()
|
@IonicPage({segment: "core-courses-my-overview"})
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'page-core-courses-my-overview',
|
selector: 'page-core-courses-my-overview',
|
||||||
templateUrl: 'my-overview.html',
|
templateUrl: 'my-overview.html',
|
||||||
|
|
|
@ -20,7 +20,7 @@ import { CoreCoursesProvider } from '../../providers/courses';
|
||||||
/**
|
/**
|
||||||
* Page that allows searching for courses.
|
* Page that allows searching for courses.
|
||||||
*/
|
*/
|
||||||
@IonicPage()
|
@IonicPage({segment: "core-courses-search"})
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'page-core-courses-search',
|
selector: 'page-core-courses-search',
|
||||||
templateUrl: 'search.html',
|
templateUrl: 'search.html',
|
||||||
|
|
|
@ -18,7 +18,7 @@ import { IonicPage, ViewController } from 'ionic-angular';
|
||||||
/**
|
/**
|
||||||
* Page that displays a form to enter a password to self enrol in a course.
|
* Page that displays a form to enter a password to self enrol in a course.
|
||||||
*/
|
*/
|
||||||
@IonicPage()
|
@IonicPage({segment: "core-courses-self-enrol-password"})
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'page-core-courses-self-enrol-password',
|
selector: 'page-core-courses-self-enrol-password',
|
||||||
templateUrl: 'self-enrol-password.html',
|
templateUrl: 'self-enrol-password.html',
|
||||||
|
|
|
@ -22,7 +22,7 @@ import { CoreCoursesMyOverviewProvider } from '../providers/my-overview';
|
||||||
*/
|
*/
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class CoreCoursesMainMenuHandler implements CoreMainMenuHandler {
|
export class CoreCoursesMainMenuHandler implements CoreMainMenuHandler {
|
||||||
name = 'mmCourses';
|
name = 'CoreCourses';
|
||||||
priority = 1100;
|
priority = 1100;
|
||||||
isOverviewEnabled: boolean;
|
isOverviewEnabled: boolean;
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ import { CoreTimeUtilsProvider } from '../../../../providers/utils/time';
|
||||||
/**
|
/**
|
||||||
* Page to capture media in browser or desktop.
|
* Page to capture media in browser or desktop.
|
||||||
*/
|
*/
|
||||||
@IonicPage()
|
@IonicPage({segment: "core-emulator-capture-media"})
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'page-core-emulator-capture-media',
|
selector: 'page-core-emulator-capture-media',
|
||||||
templateUrl: 'capture-media.html',
|
templateUrl: 'capture-media.html',
|
||||||
|
|
|
@ -26,7 +26,7 @@ import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||||
/**
|
/**
|
||||||
* Page to enter the user credentials.
|
* Page to enter the user credentials.
|
||||||
*/
|
*/
|
||||||
@IonicPage()
|
@IonicPage({segment: "core-login-credentials"})
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'page-core-login-credentials',
|
selector: 'page-core-login-credentials',
|
||||||
templateUrl: 'credentials.html',
|
templateUrl: 'credentials.html',
|
||||||
|
@ -84,10 +84,7 @@ export class CoreLoginCredentialsPage {
|
||||||
*/
|
*/
|
||||||
ionViewDidLeave() {
|
ionViewDidLeave() {
|
||||||
this.viewLeft = true;
|
this.viewLeft = true;
|
||||||
this.eventsProvider.trigger(CoreEventsProvider.LOGIN_SITE_UNCHECKED, {
|
this.eventsProvider.trigger(CoreEventsProvider.LOGIN_SITE_UNCHECKED, {config: this.siteConfig}, this.siteId);
|
||||||
siteId: this.siteId,
|
|
||||||
config: this.siteConfig
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -146,9 +143,7 @@ export class CoreLoginCredentialsPage {
|
||||||
|
|
||||||
if (!this.eventThrown && !this.viewLeft) {
|
if (!this.eventThrown && !this.viewLeft) {
|
||||||
this.eventThrown = true;
|
this.eventThrown = true;
|
||||||
this.eventsProvider.trigger(CoreEventsProvider.LOGIN_SITE_CHECKED, {
|
this.eventsProvider.trigger(CoreEventsProvider.LOGIN_SITE_CHECKED, {config: this.siteConfig});
|
||||||
config: this.siteConfig
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.siteName = null;
|
this.siteName = null;
|
||||||
|
|
|
@ -26,7 +26,7 @@ import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||||
/**
|
/**
|
||||||
* Page to signup using email.
|
* Page to signup using email.
|
||||||
*/
|
*/
|
||||||
@IonicPage()
|
@IonicPage({segment: "core-login-email-signup"})
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'page-core-login-email-signup',
|
selector: 'page-core-login-email-signup',
|
||||||
templateUrl: 'email-signup.html',
|
templateUrl: 'email-signup.html',
|
||||||
|
|
|
@ -22,7 +22,7 @@ import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||||
/**
|
/**
|
||||||
* Page to recover a forgotten password.
|
* Page to recover a forgotten password.
|
||||||
*/
|
*/
|
||||||
@IonicPage()
|
@IonicPage({segment: "core-login-forgotten-password"})
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'page-core-login-forgotten-password',
|
selector: 'page-core-login-forgotten-password',
|
||||||
templateUrl: 'forgotten-password.html',
|
templateUrl: 'forgotten-password.html',
|
||||||
|
|
|
@ -23,7 +23,7 @@ import { CoreLoginHelperProvider } from '../../providers/helper';
|
||||||
/**
|
/**
|
||||||
* Page that displays a "splash screen" while the app is being initialized.
|
* Page that displays a "splash screen" while the app is being initialized.
|
||||||
*/
|
*/
|
||||||
@IonicPage()
|
@IonicPage({segment: "core-login-init"})
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'page-core-login-init',
|
selector: 'page-core-login-init',
|
||||||
templateUrl: 'init.html',
|
templateUrl: 'init.html',
|
||||||
|
|
|
@ -23,7 +23,7 @@ import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||||
/**
|
/**
|
||||||
* Page to enter the user password to reconnect to a site.
|
* Page to enter the user password to reconnect to a site.
|
||||||
*/
|
*/
|
||||||
@IonicPage()
|
@IonicPage({segment: "core-login-reconnect"})
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'page-core-login-reconnect',
|
selector: 'page-core-login-reconnect',
|
||||||
templateUrl: 'reconnect.html',
|
templateUrl: 'reconnect.html',
|
||||||
|
|
|
@ -18,7 +18,7 @@ import { IonicPage, ViewController, NavParams } from 'ionic-angular';
|
||||||
/**
|
/**
|
||||||
* Component that displays an error when trying to connect to a site.
|
* Component that displays an error when trying to connect to a site.
|
||||||
*/
|
*/
|
||||||
@IonicPage()
|
@IonicPage({segment: "core-login-site-error"})
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'page-core-login-site-error',
|
selector: 'page-core-login-site-error',
|
||||||
templateUrl: 'site-error.html',
|
templateUrl: 'site-error.html',
|
||||||
|
|
|
@ -18,7 +18,7 @@ import { IonicPage, ViewController } from 'ionic-angular';
|
||||||
/**
|
/**
|
||||||
* Component that displays some help regarding the CoreLoginSitePage.
|
* Component that displays some help regarding the CoreLoginSitePage.
|
||||||
*/
|
*/
|
||||||
@IonicPage()
|
@IonicPage({segment: "core-login-site-help"})
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'page-core-login-site-help',
|
selector: 'page-core-login-site-help',
|
||||||
templateUrl: 'site-help.html',
|
templateUrl: 'site-help.html',
|
||||||
|
|
|
@ -23,7 +23,7 @@ import { CoreSite } from '../../../../classes/site';
|
||||||
/**
|
/**
|
||||||
* Page to accept a site policy.
|
* Page to accept a site policy.
|
||||||
*/
|
*/
|
||||||
@IonicPage()
|
@IonicPage({segment: "core-login-site-policy"})
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'page-core-login-site-policy',
|
selector: 'page-core-login-site-policy',
|
||||||
templateUrl: 'site-policy.html',
|
templateUrl: 'site-policy.html',
|
||||||
|
|
|
@ -24,7 +24,7 @@ import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||||
/**
|
/**
|
||||||
* Page to enter or select the site URL to connect to.
|
* Page to enter or select the site URL to connect to.
|
||||||
*/
|
*/
|
||||||
@IonicPage()
|
@IonicPage({segment: "core-login-site"})
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'page-core-login-site',
|
selector: 'page-core-login-site',
|
||||||
templateUrl: 'site.html',
|
templateUrl: 'site.html',
|
||||||
|
|
|
@ -24,7 +24,7 @@ import { CoreLoginHelperProvider } from '../../providers/helper';
|
||||||
/**
|
/**
|
||||||
* Page that displays the list of stored sites.
|
* Page that displays the list of stored sites.
|
||||||
*/
|
*/
|
||||||
@IonicPage()
|
@IonicPage({segment: "core-login-sites"})
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'page-core-login-sites',
|
selector: 'page-core-login-sites',
|
||||||
templateUrl: 'sites.html',
|
templateUrl: 'sites.html',
|
||||||
|
|
|
@ -574,10 +574,9 @@ export class CoreLoginHelperProvider {
|
||||||
|
|
||||||
if (site.isLoggedOut()) {
|
if (site.isLoggedOut()) {
|
||||||
this.eventsProvider.trigger(CoreEventsProvider.SESSION_EXPIRED, {
|
this.eventsProvider.trigger(CoreEventsProvider.SESSION_EXPIRED, {
|
||||||
siteId: site.getId(),
|
|
||||||
pageName: pageName,
|
pageName: pageName,
|
||||||
params: params
|
params: params
|
||||||
});
|
}, site.getId());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -22,7 +22,7 @@ import { CoreMainMenuDelegate, CoreMainMenuHandlerData } from '../../providers/d
|
||||||
/**
|
/**
|
||||||
* Page that displays the main menu of the app.
|
* Page that displays the main menu of the app.
|
||||||
*/
|
*/
|
||||||
@IonicPage()
|
@IonicPage({segment: "core-mainmenu"})
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'page-core-mainmenu',
|
selector: 'page-core-mainmenu',
|
||||||
templateUrl: 'menu.html',
|
templateUrl: 'menu.html',
|
||||||
|
|
|
@ -22,7 +22,7 @@ import { CoreMainMenuProvider, CoreMainMenuCustomItem } from '../../providers/ma
|
||||||
/**
|
/**
|
||||||
* Page that displays the list of main menu options that aren't in the tabs.
|
* Page that displays the list of main menu options that aren't in the tabs.
|
||||||
*/
|
*/
|
||||||
@IonicPage()
|
@IonicPage({segment: "core-mainmenu-more"})
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'page-core-mainmenu-more',
|
selector: 'page-core-mainmenu-more',
|
||||||
templateUrl: 'more.html',
|
templateUrl: 'more.html',
|
||||||
|
@ -45,12 +45,8 @@ export class CoreMainMenuMorePage implements OnDestroy {
|
||||||
private navCtrl: NavController, private mainMenuProvider: CoreMainMenuProvider, eventsProvider: CoreEventsProvider) {
|
private navCtrl: NavController, private mainMenuProvider: CoreMainMenuProvider, eventsProvider: CoreEventsProvider) {
|
||||||
|
|
||||||
this.langObserver = eventsProvider.on(CoreEventsProvider.LANGUAGE_CHANGED, this.loadSiteInfo.bind(this));
|
this.langObserver = eventsProvider.on(CoreEventsProvider.LANGUAGE_CHANGED, this.loadSiteInfo.bind(this));
|
||||||
this.updateSiteObserver = eventsProvider.on(CoreEventsProvider.SITE_UPDATED, (data) => {
|
this.updateSiteObserver = eventsProvider.on(CoreEventsProvider.SITE_UPDATED, this.loadSiteInfo.bind(this),
|
||||||
if (sitesProvider.getCurrentSiteId() == data.siteId) {
|
sitesProvider.getCurrentSiteId());
|
||||||
this.loadSiteInfo();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.loadSiteInfo();
|
this.loadSiteInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ import { CoreSharedFilesHelperProvider } from '../../providers/helper';
|
||||||
/**
|
/**
|
||||||
* Modal to display the list of sites to choose one to store a shared file.
|
* Modal to display the list of sites to choose one to store a shared file.
|
||||||
*/
|
*/
|
||||||
@IonicPage()
|
@IonicPage({segment: "core-shared-files-choose-site"})
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'page-core-shared-files-choose-site',
|
selector: 'page-core-shared-files-choose-site',
|
||||||
templateUrl: 'choose-site.html',
|
templateUrl: 'choose-site.html',
|
||||||
|
|
|
@ -24,7 +24,7 @@ import { CoreSharedFilesProvider } from '../../providers/sharedfiles';
|
||||||
/**
|
/**
|
||||||
* Modal to display the list of shared files.
|
* Modal to display the list of shared files.
|
||||||
*/
|
*/
|
||||||
@IonicPage()
|
@IonicPage({segment: "core-shared-files-list"})
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'page-core-shared-files-list',
|
selector: 'page-core-shared-files-list',
|
||||||
templateUrl: 'list.html',
|
templateUrl: 'list.html',
|
||||||
|
|
|
@ -18,7 +18,7 @@ import { IonicPage, NavParams } from 'ionic-angular';
|
||||||
/**
|
/**
|
||||||
* Page to display a URL in an iframe.
|
* Page to display a URL in an iframe.
|
||||||
*/
|
*/
|
||||||
@IonicPage()
|
@IonicPage({segment: "core-viewer-iframe"})
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'page-core-viewer-iframe',
|
selector: 'page-core-viewer-iframe',
|
||||||
templateUrl: 'iframe.html',
|
templateUrl: 'iframe.html',
|
||||||
|
|
|
@ -19,7 +19,7 @@ import { CoreTextUtilsProvider } from '../../../../providers/utils/text';
|
||||||
/**
|
/**
|
||||||
* Page to render a certain text. If opened as a modal, it will have a button to close the modal.
|
* Page to render a certain text. If opened as a modal, it will have a button to close the modal.
|
||||||
*/
|
*/
|
||||||
@IonicPage()
|
@IonicPage({segment: "core-viewer-text"})
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'page-core-viewer-text',
|
selector: 'page-core-viewer-text',
|
||||||
templateUrl: 'text.html',
|
templateUrl: 'text.html',
|
||||||
|
|
|
@ -184,7 +184,7 @@ export class CoreAppProvider {
|
||||||
return online;
|
return online;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Check if device uses a limited connection.
|
* Check if device uses a limited connection.
|
||||||
*
|
*
|
||||||
* @return {boolean} Whether the device uses a limited connection.
|
* @return {boolean} Whether the device uses a limited connection.
|
||||||
|
|
|
@ -64,9 +64,10 @@ export class CoreEventsProvider {
|
||||||
*
|
*
|
||||||
* @param {string} eventName Name of the event to listen to.
|
* @param {string} eventName Name of the event to listen to.
|
||||||
* @param {Function} callBack Function to call when the event is triggered.
|
* @param {Function} callBack Function to call when the event is triggered.
|
||||||
|
* @param {string} [siteId] Site where to trigger the event. Undefined won't check the site.
|
||||||
* @return {CoreEventObserver} Observer to stop listening.
|
* @return {CoreEventObserver} Observer to stop listening.
|
||||||
*/
|
*/
|
||||||
on(eventName: string, callBack: (value: any) => void) : CoreEventObserver {
|
on(eventName: string, callBack: (value: any) => void, siteId?: string) : CoreEventObserver {
|
||||||
// If it's a unique event and has been triggered already, call the callBack.
|
// If it's a unique event and has been triggered already, call the callBack.
|
||||||
// We don't need to create an observer because the event won't be triggered again.
|
// We don't need to create an observer because the event won't be triggered again.
|
||||||
if (this.uniqueEvents[eventName]) {
|
if (this.uniqueEvents[eventName]) {
|
||||||
|
@ -84,7 +85,11 @@ export class CoreEventsProvider {
|
||||||
this.observables[eventName] = new Subject<any>();
|
this.observables[eventName] = new Subject<any>();
|
||||||
}
|
}
|
||||||
|
|
||||||
let subscription = this.observables[eventName].subscribe(callBack);
|
let subscription = this.observables[eventName].subscribe((value: any) => {
|
||||||
|
if (!siteId || value.siteId == siteId) {
|
||||||
|
callBack(value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Create and return a CoreEventObserver.
|
// Create and return a CoreEventObserver.
|
||||||
return {
|
return {
|
||||||
|
@ -100,10 +105,17 @@ export class CoreEventsProvider {
|
||||||
*
|
*
|
||||||
* @param {string} event Name of the event to trigger.
|
* @param {string} event Name of the event to trigger.
|
||||||
* @param {any} [data] Data to pass to the observers.
|
* @param {any} [data] Data to pass to the observers.
|
||||||
|
* @param {string} [siteId] Site where to trigger the event. Undefined means no Site.
|
||||||
*/
|
*/
|
||||||
trigger(eventName: string, data?: any) : void {
|
trigger(eventName: string, data?: any, siteId?: string) : void {
|
||||||
this.logger.debug(`Event '${eventName}' triggered.`);
|
this.logger.debug(`Event '${eventName}' triggered.`);
|
||||||
if (this.observables[eventName]) {
|
if (this.observables[eventName]) {
|
||||||
|
if (siteId) {
|
||||||
|
if (!data) {
|
||||||
|
data = {};
|
||||||
|
}
|
||||||
|
data.siteId = siteId;
|
||||||
|
}
|
||||||
this.observables[eventName].next(data);
|
this.observables[eventName].next(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -113,12 +125,21 @@ export class CoreEventsProvider {
|
||||||
*
|
*
|
||||||
* @param {string} event Name of the event to trigger.
|
* @param {string} event Name of the event to trigger.
|
||||||
* @param {any} data Data to pass to the observers.
|
* @param {any} data Data to pass to the observers.
|
||||||
|
* @param {string} [siteId] Site where to trigger the event. Undefined means no Site.
|
||||||
*/
|
*/
|
||||||
triggerUnique(eventName: string, data: any) : void {
|
triggerUnique(eventName: string, data: any, siteId?: string) : void {
|
||||||
if (this.uniqueEvents[eventName]) {
|
if (this.uniqueEvents[eventName]) {
|
||||||
this.logger.debug(`Unique event '${eventName}' ignored because it was already triggered.`);
|
this.logger.debug(`Unique event '${eventName}' ignored because it was already triggered.`);
|
||||||
} else {
|
} else {
|
||||||
this.logger.debug(`Unique event '${eventName}' triggered.`);
|
this.logger.debug(`Unique event '${eventName}' triggered.`);
|
||||||
|
|
||||||
|
if (siteId) {
|
||||||
|
if (!data) {
|
||||||
|
data = {};
|
||||||
|
}
|
||||||
|
data.siteId = siteId;
|
||||||
|
}
|
||||||
|
|
||||||
// Store the data so it can be passed to observers that register from now on.
|
// Store the data so it can be passed to observers that register from now on.
|
||||||
this.uniqueEvents[eventName] = {
|
this.uniqueEvents[eventName] = {
|
||||||
data: data
|
data: data
|
||||||
|
|
|
@ -2692,12 +2692,11 @@ export class CoreFilepoolProvider {
|
||||||
*/
|
*/
|
||||||
protected triggerPackageStatusChanged(siteId: string, status: string, component: string, componentId?: string|number) : void {
|
protected triggerPackageStatusChanged(siteId: string, status: string, component: string, componentId?: string|number) : void {
|
||||||
const data = {
|
const data = {
|
||||||
siteid: siteId,
|
|
||||||
component: component,
|
component: component,
|
||||||
componentId: this.fixComponentId(componentId),
|
componentId: this.fixComponentId(componentId),
|
||||||
status: status
|
status: status
|
||||||
}
|
}
|
||||||
this.eventsProvider.trigger(CoreEventsProvider.PACKAGE_STATUS_CHANGED, data);
|
this.eventsProvider.trigger(CoreEventsProvider.PACKAGE_STATUS_CHANGED, data, siteId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -398,7 +398,7 @@ export class CoreSitesProvider {
|
||||||
this.sites[siteId] = candidateSite;
|
this.sites[siteId] = candidateSite;
|
||||||
// Store session.
|
// Store session.
|
||||||
this.login(siteId);
|
this.login(siteId);
|
||||||
this.eventsProvider.trigger(CoreEventsProvider.SITE_ADDED, siteId);
|
this.eventsProvider.trigger(CoreEventsProvider.SITE_ADDED, {}, siteId);
|
||||||
|
|
||||||
if (this.siteTablesSchemas.length) {
|
if (this.siteTablesSchemas.length) {
|
||||||
// Create tables in the site's database.
|
// Create tables in the site's database.
|
||||||
|
@ -558,7 +558,7 @@ export class CoreSitesProvider {
|
||||||
// Check if local_mobile was installed to Moodle.
|
// Check if local_mobile was installed to Moodle.
|
||||||
return site.checkIfLocalMobileInstalledAndNotUsed().then(() => {
|
return site.checkIfLocalMobileInstalledAndNotUsed().then(() => {
|
||||||
// Local mobile was added. Throw invalid session to force reconnect and create a new token.
|
// Local mobile was added. Throw invalid session to force reconnect and create a new token.
|
||||||
this.eventsProvider.trigger(CoreEventsProvider.SESSION_EXPIRED, {siteId: siteId});
|
this.eventsProvider.trigger(CoreEventsProvider.SESSION_EXPIRED, {}, siteId);
|
||||||
}, () => {
|
}, () => {
|
||||||
// Update site info. We don't block the UI.
|
// Update site info. We don't block the UI.
|
||||||
this.updateSiteInfo(siteId);
|
this.updateSiteInfo(siteId);
|
||||||
|
@ -622,7 +622,7 @@ export class CoreSitesProvider {
|
||||||
// DB remove shouldn't fail, but we'll go ahead even if it does.
|
// DB remove shouldn't fail, but we'll go ahead even if it does.
|
||||||
return site.deleteFolder();
|
return site.deleteFolder();
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
this.eventsProvider.trigger(CoreEventsProvider.SITE_DELETED, site);
|
this.eventsProvider.trigger(CoreEventsProvider.SITE_DELETED, site, siteId);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -739,7 +739,7 @@ export class CoreSitesProvider {
|
||||||
* @param {number} [siteId] The site ID. If not defined, current site (if available).
|
* @param {number} [siteId] The site ID. If not defined, current site (if available).
|
||||||
* @return {Promise} Promise resolved with site home ID.
|
* @return {Promise} Promise resolved with site home ID.
|
||||||
*/
|
*/
|
||||||
getSiteHomeId(siteId: string) : Promise<number> {
|
getSiteHomeId(siteId?: string) : Promise<number> {
|
||||||
return this.getSite(siteId).then((site) => {
|
return this.getSite(siteId).then((site) => {
|
||||||
return site.getSiteHomeId();
|
return site.getSiteHomeId();
|
||||||
});
|
});
|
||||||
|
@ -801,7 +801,7 @@ export class CoreSitesProvider {
|
||||||
siteId: siteId
|
siteId: siteId
|
||||||
};
|
};
|
||||||
return this.appDB.insertOrUpdateRecord(this.CURRENT_SITE_TABLE, entry, {id: 1}).then(() => {
|
return this.appDB.insertOrUpdateRecord(this.CURRENT_SITE_TABLE, entry, {id: 1}).then(() => {
|
||||||
this.eventsProvider.trigger(CoreEventsProvider.LOGIN, {siteId: siteId});
|
this.eventsProvider.trigger(CoreEventsProvider.LOGIN, {}, siteId);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -829,7 +829,7 @@ export class CoreSitesProvider {
|
||||||
promises.push(this.appDB.deleteRecords(this.CURRENT_SITE_TABLE, {id: 1}));
|
promises.push(this.appDB.deleteRecords(this.CURRENT_SITE_TABLE, {id: 1}));
|
||||||
|
|
||||||
return Promise.all(promises).finally(() => {
|
return Promise.all(promises).finally(() => {
|
||||||
this.eventsProvider.trigger(CoreEventsProvider.LOGOUT, {siteId: siteId});
|
this.eventsProvider.trigger(CoreEventsProvider.LOGOUT, {}, siteId);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -936,7 +936,7 @@ export class CoreSitesProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.appDB.updateRecords(this.SITES_TABLE, newValues, {id: siteId}).finally(() => {
|
return this.appDB.updateRecords(this.SITES_TABLE, newValues, {id: siteId}).finally(() => {
|
||||||
this.eventsProvider.trigger(CoreEventsProvider.SITE_UPDATED, {siteId: siteId});
|
this.eventsProvider.trigger(CoreEventsProvider.SITE_UPDATED, {}, siteId);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -323,7 +323,7 @@ export class CoreMimetypeUtilsProvider {
|
||||||
let filename = '',
|
let filename = '',
|
||||||
mimetype = '',
|
mimetype = '',
|
||||||
extension = '',
|
extension = '',
|
||||||
langPrefix = 'core.mimetype-';
|
langPrefix = 'assets.mimetypes.';
|
||||||
|
|
||||||
if (typeof obj == 'object' && typeof obj.file == 'function') {
|
if (typeof obj == 'object' && typeof obj.file == 'function') {
|
||||||
// It's a FileEntry. Don't use the file function because it's asynchronous and the type isn't reliable.
|
// It's a FileEntry. Don't use the file function because it's asynchronous and the type isn't reliable.
|
||||||
|
@ -422,7 +422,7 @@ export class CoreMimetypeUtilsProvider {
|
||||||
* @return {string} Translated name.
|
* @return {string} Translated name.
|
||||||
*/
|
*/
|
||||||
getTranslatedGroupName(name: string) : string {
|
getTranslatedGroupName(name: string) : string {
|
||||||
let key = 'core.mimetype-group:' + name,
|
let key = 'assets.mimetypes.group:' + name,
|
||||||
translated = this.translate.instant(key);
|
translated = this.translate.instant(key);
|
||||||
return translated != key ? translated : name;
|
return translated != key ? translated : name;
|
||||||
}
|
}
|
||||||
|
|
|
@ -552,7 +552,7 @@ export class CoreUtilsProvider {
|
||||||
* @return {string} Country name. If the country is not found, return the country code.
|
* @return {string} Country name. If the country is not found, return the country code.
|
||||||
*/
|
*/
|
||||||
getCountryName(code: string) : string {
|
getCountryName(code: string) : string {
|
||||||
let countryKey = 'core.country-' + code,
|
let countryKey = 'assets.countries.' + code,
|
||||||
countryName = this.translate.instant(countryKey);
|
countryName = this.translate.instant(countryKey);
|
||||||
|
|
||||||
return countryName !== countryKey ? countryName : code;
|
return countryName !== countryKey ? countryName : code;
|
||||||
|
@ -580,8 +580,8 @@ export class CoreUtilsProvider {
|
||||||
let countries = {};
|
let countries = {};
|
||||||
|
|
||||||
for (let name in table) {
|
for (let name in table) {
|
||||||
if (name.indexOf('core.country-') === 0) {
|
if (name.indexOf('assets.countries.') === 0) {
|
||||||
let code = name.replace('core.country-', '');
|
let code = name.replace('assets.countries.', '');
|
||||||
countries[code] = table[name];
|
countries[code] = table[name];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
10
tslint.json
10
tslint.json
|
@ -3,7 +3,15 @@
|
||||||
"no-duplicate-variable": true,
|
"no-duplicate-variable": true,
|
||||||
"no-unused-variable": [
|
"no-unused-variable": [
|
||||||
true
|
true
|
||||||
]
|
],
|
||||||
|
"max-line-length": {
|
||||||
|
"options": [132]
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"jsRules": {
|
||||||
|
"max-line-length": {
|
||||||
|
"options": [132]
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"rulesDirectory": [
|
"rulesDirectory": [
|
||||||
"node_modules/tslint-eslint-rules/dist/rules"
|
"node_modules/tslint-eslint-rules/dist/rules"
|
||||||
|
|
Loading…
Reference in New Issue