commit
ece285db4e
60
gulpfile.js
60
gulpfile.js
|
@ -59,39 +59,39 @@ function treatMergedData(data) {
|
|||
var mergedOrdered = {};
|
||||
|
||||
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) {
|
||||
|
||||
var componentName = filepath.replace('core/', '');
|
||||
componentName = componentName.substr(0, componentName.indexOf('/'));
|
||||
addProperties(merged, data[filepath], 'core.'+componentName+'.');
|
||||
|
||||
} else if (filepath.indexOf('addons') === 0) {
|
||||
|
||||
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-');
|
||||
// For subplugins. We'll use plugin_subfolder_subfolder2_...
|
||||
// E.g. 'mod_assign_feedback_comments'.
|
||||
prefix = 'addon.' + pathSplit.join('_');
|
||||
break;
|
||||
case 'assets':
|
||||
prefix = 'assets.' + pathSplit[1];
|
||||
break;
|
||||
}
|
||||
|
||||
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: [
|
||||
'./src/lang/',
|
||||
'./src/core/**/lang/',
|
||||
'./src/addons/**/lang/',
|
||||
'./src/addon/**/lang/',
|
||||
'./src/assets/countries/',
|
||||
'./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.
|
||||
@if ($core-text-input-ios-show-highlight) {
|
||||
|
@ -87,4 +90,4 @@
|
|||
@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)});
|
||||
}
|
||||
|
||||
.bar-buttons core-context-menu .button-clear-md {
|
||||
color: $toolbar-md-button-color;
|
||||
}
|
||||
|
||||
// Highlights inside the input element.
|
||||
@if ($core-text-input-md-show-highlight) {
|
||||
.card-md, .list-md {
|
||||
|
@ -75,4 +79,4 @@
|
|||
@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 { CoreFileUploaderModule } from '../core/fileuploader/fileuploader.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.
|
||||
export function createTranslateLoader(http: HttpClient) {
|
||||
|
@ -86,7 +86,8 @@ export function createTranslateLoader(http: HttpClient) {
|
|||
CoreCoursesModule,
|
||||
CoreFileUploaderModule,
|
||||
CoreSharedFilesModule,
|
||||
CoreComponentsModule
|
||||
CoreComponentsModule,
|
||||
AddonCalendarModule
|
||||
],
|
||||
bootstrap: [IonicApp],
|
||||
entryComponents: [
|
||||
|
|
|
@ -11,3 +11,7 @@
|
|||
.col[align-self-stretch] .card-wp {
|
||||
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.
|
||||
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.
|
||||
error.message = this.translate.instant('core.lostconnection');
|
||||
} else if (error.errorcode === 'userdeleted') {
|
||||
// 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');
|
||||
return Promise.reject(error);
|
||||
} else if (error.errorcode === 'forcepasswordchangenotice') {
|
||||
// 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');
|
||||
return Promise.reject(error);
|
||||
} else if (error.errorcode === 'usernotfullysetup') {
|
||||
// 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');
|
||||
return Promise.reject(error);
|
||||
} else if (error.errorcode === 'sitepolicynotagreed') {
|
||||
// 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');
|
||||
return Promise.reject(error);
|
||||
} 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);
|
||||
let sql = 'UPDATE ' + this.WS_CACHE_TABLE + ' SET expirationTime=0 WHERE key LIKE ?%';
|
||||
return this.db.execute(sql, [key]);
|
||||
let sql = 'UPDATE ' + this.WS_CACHE_TABLE + ' SET expirationTime=0 WHERE key LIKE ?';
|
||||
return this.db.execute(sql, [key + "%"]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -21,6 +21,7 @@ import { CoreLoadingComponent } from './loading/loading';
|
|||
import { CoreMarkRequiredComponent } from './mark-required/mark-required';
|
||||
import { CoreInputErrorsComponent } from './input-errors/input-errors';
|
||||
import { CoreShowPasswordComponent } from './show-password/show-password';
|
||||
import { CoreSplitViewComponent } from './split-view/split-view';
|
||||
import { CoreIframeComponent } from './iframe/iframe';
|
||||
import { CoreProgressBarComponent } from './progress-bar/progress-bar';
|
||||
import { CoreEmptyBoxComponent } from './empty-box/empty-box';
|
||||
|
@ -29,6 +30,7 @@ import { CoreFileComponent } from './file/file';
|
|||
import { CoreContextMenuComponent } from './context-menu/context-menu';
|
||||
import { CoreContextMenuItemComponent } from './context-menu/context-menu-item';
|
||||
import { CoreContextMenuPopoverComponent } from './context-menu/context-menu-popover';
|
||||
import { CoreCoursePickerMenuPopoverComponent } from './course-picker-menu/course-picker-menu-popover';
|
||||
import { CoreChronoComponent } from './chrono/chrono';
|
||||
import { CoreLocalFileComponent } from './local-file/local-file';
|
||||
import { CoreSitePickerComponent } from './site-picker/site-picker';
|
||||
|
@ -39,6 +41,7 @@ import { CoreSitePickerComponent } from './site-picker/site-picker';
|
|||
CoreMarkRequiredComponent,
|
||||
CoreInputErrorsComponent,
|
||||
CoreShowPasswordComponent,
|
||||
CoreSplitViewComponent,
|
||||
CoreIframeComponent,
|
||||
CoreProgressBarComponent,
|
||||
CoreEmptyBoxComponent,
|
||||
|
@ -47,12 +50,14 @@ import { CoreSitePickerComponent } from './site-picker/site-picker';
|
|||
CoreContextMenuComponent,
|
||||
CoreContextMenuItemComponent,
|
||||
CoreContextMenuPopoverComponent,
|
||||
CoreCoursePickerMenuPopoverComponent,
|
||||
CoreChronoComponent,
|
||||
CoreLocalFileComponent,
|
||||
CoreSitePickerComponent
|
||||
],
|
||||
entryComponents: [
|
||||
CoreContextMenuPopoverComponent
|
||||
CoreContextMenuPopoverComponent,
|
||||
CoreCoursePickerMenuPopoverComponent
|
||||
],
|
||||
imports: [
|
||||
IonicModule,
|
||||
|
@ -65,6 +70,7 @@ import { CoreSitePickerComponent } from './site-picker/site-picker';
|
|||
CoreMarkRequiredComponent,
|
||||
CoreInputErrorsComponent,
|
||||
CoreShowPasswordComponent,
|
||||
CoreSplitViewComponent,
|
||||
CoreIframeComponent,
|
||||
CoreProgressBarComponent,
|
||||
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.
|
||||
*/
|
||||
@IonicPage()
|
||||
@IonicPage({segment: "core-courses-available-courses"})
|
||||
@Component({
|
||||
selector: 'page-core-courses-available-courses',
|
||||
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.
|
||||
*/
|
||||
@IonicPage()
|
||||
@IonicPage({segment: "core-courses-categories"})
|
||||
@Component({
|
||||
selector: 'page-core-courses-categories',
|
||||
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.
|
||||
*/
|
||||
@IonicPage()
|
||||
@IonicPage({segment: "core-courses-course-preview"})
|
||||
@Component({
|
||||
selector: 'page-core-courses-course-preview',
|
||||
templateUrl: 'course-preview.html',
|
||||
|
@ -307,7 +307,7 @@ export class CoreCoursesCoursePreviewPage implements OnDestroy {
|
|||
this.refreshData().finally(() => {
|
||||
// My courses have been updated, trigger event.
|
||||
this.eventsProvider.trigger(
|
||||
CoreCoursesProvider.EVENT_MY_COURSES_UPDATED, {siteId: this.sitesProvider.getCurrentSiteId()});
|
||||
CoreCoursesProvider.EVENT_MY_COURSES_UPDATED, {}, this.sitesProvider.getCurrentSiteId());
|
||||
});
|
||||
});
|
||||
}).catch((error) => {
|
||||
|
|
|
@ -22,7 +22,7 @@ import { CoreCoursesProvider } from '../../providers/courses';
|
|||
/**
|
||||
* Page that displays the list of courses the user is enrolled in.
|
||||
*/
|
||||
@IonicPage()
|
||||
@IonicPage({segment: "core-courses-my-courses"})
|
||||
@Component({
|
||||
selector: 'page-core-courses-my-courses',
|
||||
templateUrl: 'my-courses.html',
|
||||
|
@ -53,17 +53,13 @@ export class CoreCoursesMyCoursesPage implements OnDestroy {
|
|||
this.coursesLoaded = true;
|
||||
});
|
||||
|
||||
this.myCoursesObserver = this.eventsProvider.on(CoreCoursesProvider.EVENT_MY_COURSES_UPDATED, (data) => {
|
||||
if (data.siteId == this.sitesProvider.getCurrentSiteId()) {
|
||||
this.fetchCourses();
|
||||
}
|
||||
});
|
||||
this.myCoursesObserver = this.eventsProvider.on(CoreCoursesProvider.EVENT_MY_COURSES_UPDATED, () => {
|
||||
this.fetchCourses();
|
||||
}, this.sitesProvider.getCurrentSiteId());
|
||||
|
||||
this.siteUpdatedObserver = this.eventsProvider.on(CoreEventsProvider.SITE_UPDATED, (data) => {
|
||||
if (data.siteId == this.sitesProvider.getCurrentSiteId()) {
|
||||
this.searchEnabled = !this.coursesProvider.isSearchCoursesDisabledInSite();
|
||||
}
|
||||
});
|
||||
this.siteUpdatedObserver = this.eventsProvider.on(CoreEventsProvider.SITE_UPDATED, () => {
|
||||
this.searchEnabled = !this.coursesProvider.isSearchCoursesDisabledInSite();
|
||||
}, this.sitesProvider.getCurrentSiteId());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -22,7 +22,7 @@ import * as moment from 'moment';
|
|||
/**
|
||||
* Page that displays My Overview.
|
||||
*/
|
||||
@IonicPage()
|
||||
@IonicPage({segment: "core-courses-my-overview"})
|
||||
@Component({
|
||||
selector: 'page-core-courses-my-overview',
|
||||
templateUrl: 'my-overview.html',
|
||||
|
|
|
@ -20,7 +20,7 @@ import { CoreCoursesProvider } from '../../providers/courses';
|
|||
/**
|
||||
* Page that allows searching for courses.
|
||||
*/
|
||||
@IonicPage()
|
||||
@IonicPage({segment: "core-courses-search"})
|
||||
@Component({
|
||||
selector: 'page-core-courses-search',
|
||||
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.
|
||||
*/
|
||||
@IonicPage()
|
||||
@IonicPage({segment: "core-courses-self-enrol-password"})
|
||||
@Component({
|
||||
selector: 'page-core-courses-self-enrol-password',
|
||||
templateUrl: 'self-enrol-password.html',
|
||||
|
|
|
@ -22,7 +22,7 @@ import { CoreCoursesMyOverviewProvider } from '../providers/my-overview';
|
|||
*/
|
||||
@Injectable()
|
||||
export class CoreCoursesMainMenuHandler implements CoreMainMenuHandler {
|
||||
name = 'mmCourses';
|
||||
name = 'CoreCourses';
|
||||
priority = 1100;
|
||||
isOverviewEnabled: boolean;
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ import { CoreTimeUtilsProvider } from '../../../../providers/utils/time';
|
|||
/**
|
||||
* Page to capture media in browser or desktop.
|
||||
*/
|
||||
@IonicPage()
|
||||
@IonicPage({segment: "core-emulator-capture-media"})
|
||||
@Component({
|
||||
selector: 'page-core-emulator-capture-media',
|
||||
templateUrl: 'capture-media.html',
|
||||
|
|
|
@ -26,7 +26,7 @@ import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
|||
/**
|
||||
* Page to enter the user credentials.
|
||||
*/
|
||||
@IonicPage()
|
||||
@IonicPage({segment: "core-login-credentials"})
|
||||
@Component({
|
||||
selector: 'page-core-login-credentials',
|
||||
templateUrl: 'credentials.html',
|
||||
|
@ -84,10 +84,7 @@ export class CoreLoginCredentialsPage {
|
|||
*/
|
||||
ionViewDidLeave() {
|
||||
this.viewLeft = true;
|
||||
this.eventsProvider.trigger(CoreEventsProvider.LOGIN_SITE_UNCHECKED, {
|
||||
siteId: this.siteId,
|
||||
config: this.siteConfig
|
||||
});
|
||||
this.eventsProvider.trigger(CoreEventsProvider.LOGIN_SITE_UNCHECKED, {config: this.siteConfig}, this.siteId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -146,9 +143,7 @@ export class CoreLoginCredentialsPage {
|
|||
|
||||
if (!this.eventThrown && !this.viewLeft) {
|
||||
this.eventThrown = true;
|
||||
this.eventsProvider.trigger(CoreEventsProvider.LOGIN_SITE_CHECKED, {
|
||||
config: this.siteConfig
|
||||
});
|
||||
this.eventsProvider.trigger(CoreEventsProvider.LOGIN_SITE_CHECKED, {config: this.siteConfig});
|
||||
}
|
||||
} else {
|
||||
this.siteName = null;
|
||||
|
|
|
@ -26,7 +26,7 @@ import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
|||
/**
|
||||
* Page to signup using email.
|
||||
*/
|
||||
@IonicPage()
|
||||
@IonicPage({segment: "core-login-email-signup"})
|
||||
@Component({
|
||||
selector: 'page-core-login-email-signup',
|
||||
templateUrl: 'email-signup.html',
|
||||
|
|
|
@ -22,7 +22,7 @@ import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
|||
/**
|
||||
* Page to recover a forgotten password.
|
||||
*/
|
||||
@IonicPage()
|
||||
@IonicPage({segment: "core-login-forgotten-password"})
|
||||
@Component({
|
||||
selector: 'page-core-login-forgotten-password',
|
||||
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.
|
||||
*/
|
||||
@IonicPage()
|
||||
@IonicPage({segment: "core-login-init"})
|
||||
@Component({
|
||||
selector: 'page-core-login-init',
|
||||
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.
|
||||
*/
|
||||
@IonicPage()
|
||||
@IonicPage({segment: "core-login-reconnect"})
|
||||
@Component({
|
||||
selector: 'page-core-login-reconnect',
|
||||
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.
|
||||
*/
|
||||
@IonicPage()
|
||||
@IonicPage({segment: "core-login-site-error"})
|
||||
@Component({
|
||||
selector: 'page-core-login-site-error',
|
||||
templateUrl: 'site-error.html',
|
||||
|
|
|
@ -18,7 +18,7 @@ import { IonicPage, ViewController } from 'ionic-angular';
|
|||
/**
|
||||
* Component that displays some help regarding the CoreLoginSitePage.
|
||||
*/
|
||||
@IonicPage()
|
||||
@IonicPage({segment: "core-login-site-help"})
|
||||
@Component({
|
||||
selector: 'page-core-login-site-help',
|
||||
templateUrl: 'site-help.html',
|
||||
|
|
|
@ -23,7 +23,7 @@ import { CoreSite } from '../../../../classes/site';
|
|||
/**
|
||||
* Page to accept a site policy.
|
||||
*/
|
||||
@IonicPage()
|
||||
@IonicPage({segment: "core-login-site-policy"})
|
||||
@Component({
|
||||
selector: 'page-core-login-site-policy',
|
||||
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.
|
||||
*/
|
||||
@IonicPage()
|
||||
@IonicPage({segment: "core-login-site"})
|
||||
@Component({
|
||||
selector: 'page-core-login-site',
|
||||
templateUrl: 'site.html',
|
||||
|
|
|
@ -24,7 +24,7 @@ import { CoreLoginHelperProvider } from '../../providers/helper';
|
|||
/**
|
||||
* Page that displays the list of stored sites.
|
||||
*/
|
||||
@IonicPage()
|
||||
@IonicPage({segment: "core-login-sites"})
|
||||
@Component({
|
||||
selector: 'page-core-login-sites',
|
||||
templateUrl: 'sites.html',
|
||||
|
|
|
@ -574,10 +574,9 @@ export class CoreLoginHelperProvider {
|
|||
|
||||
if (site.isLoggedOut()) {
|
||||
this.eventsProvider.trigger(CoreEventsProvider.SESSION_EXPIRED, {
|
||||
siteId: site.getId(),
|
||||
pageName: pageName,
|
||||
params: params
|
||||
});
|
||||
}, site.getId());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
|
@ -22,7 +22,7 @@ import { CoreMainMenuDelegate, CoreMainMenuHandlerData } from '../../providers/d
|
|||
/**
|
||||
* Page that displays the main menu of the app.
|
||||
*/
|
||||
@IonicPage()
|
||||
@IonicPage({segment: "core-mainmenu"})
|
||||
@Component({
|
||||
selector: 'page-core-mainmenu',
|
||||
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.
|
||||
*/
|
||||
@IonicPage()
|
||||
@IonicPage({segment: "core-mainmenu-more"})
|
||||
@Component({
|
||||
selector: 'page-core-mainmenu-more',
|
||||
templateUrl: 'more.html',
|
||||
|
@ -45,12 +45,8 @@ export class CoreMainMenuMorePage implements OnDestroy {
|
|||
private navCtrl: NavController, private mainMenuProvider: CoreMainMenuProvider, eventsProvider: CoreEventsProvider) {
|
||||
|
||||
this.langObserver = eventsProvider.on(CoreEventsProvider.LANGUAGE_CHANGED, this.loadSiteInfo.bind(this));
|
||||
this.updateSiteObserver = eventsProvider.on(CoreEventsProvider.SITE_UPDATED, (data) => {
|
||||
if (sitesProvider.getCurrentSiteId() == data.siteId) {
|
||||
this.loadSiteInfo();
|
||||
}
|
||||
});
|
||||
|
||||
this.updateSiteObserver = eventsProvider.on(CoreEventsProvider.SITE_UPDATED, this.loadSiteInfo.bind(this),
|
||||
sitesProvider.getCurrentSiteId());
|
||||
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.
|
||||
*/
|
||||
@IonicPage()
|
||||
@IonicPage({segment: "core-shared-files-choose-site"})
|
||||
@Component({
|
||||
selector: 'page-core-shared-files-choose-site',
|
||||
templateUrl: 'choose-site.html',
|
||||
|
|
|
@ -24,7 +24,7 @@ import { CoreSharedFilesProvider } from '../../providers/sharedfiles';
|
|||
/**
|
||||
* Modal to display the list of shared files.
|
||||
*/
|
||||
@IonicPage()
|
||||
@IonicPage({segment: "core-shared-files-list"})
|
||||
@Component({
|
||||
selector: 'page-core-shared-files-list',
|
||||
templateUrl: 'list.html',
|
||||
|
|
|
@ -18,7 +18,7 @@ import { IonicPage, NavParams } from 'ionic-angular';
|
|||
/**
|
||||
* Page to display a URL in an iframe.
|
||||
*/
|
||||
@IonicPage()
|
||||
@IonicPage({segment: "core-viewer-iframe"})
|
||||
@Component({
|
||||
selector: 'page-core-viewer-iframe',
|
||||
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.
|
||||
*/
|
||||
@IonicPage()
|
||||
@IonicPage({segment: "core-viewer-text"})
|
||||
@Component({
|
||||
selector: 'page-core-viewer-text',
|
||||
templateUrl: 'text.html',
|
||||
|
|
|
@ -184,7 +184,7 @@ export class CoreAppProvider {
|
|||
return online;
|
||||
}
|
||||
|
||||
/*
|
||||
/**
|
||||
* Check if 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 {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.
|
||||
*/
|
||||
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.
|
||||
// We don't need to create an observer because the event won't be triggered again.
|
||||
if (this.uniqueEvents[eventName]) {
|
||||
|
@ -84,7 +85,11 @@ export class CoreEventsProvider {
|
|||
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.
|
||||
return {
|
||||
|
@ -100,10 +105,17 @@ export class CoreEventsProvider {
|
|||
*
|
||||
* @param {string} event Name of the event to trigger.
|
||||
* @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.`);
|
||||
if (this.observables[eventName]) {
|
||||
if (siteId) {
|
||||
if (!data) {
|
||||
data = {};
|
||||
}
|
||||
data.siteId = siteId;
|
||||
}
|
||||
this.observables[eventName].next(data);
|
||||
}
|
||||
}
|
||||
|
@ -113,12 +125,21 @@ export class CoreEventsProvider {
|
|||
*
|
||||
* @param {string} event Name of the event to trigger.
|
||||
* @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]) {
|
||||
this.logger.debug(`Unique event '${eventName}' ignored because it was already triggered.`);
|
||||
} else {
|
||||
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.
|
||||
this.uniqueEvents[eventName] = {
|
||||
data: data
|
||||
|
|
|
@ -2692,12 +2692,11 @@ export class CoreFilepoolProvider {
|
|||
*/
|
||||
protected triggerPackageStatusChanged(siteId: string, status: string, component: string, componentId?: string|number) : void {
|
||||
const data = {
|
||||
siteid: siteId,
|
||||
component: component,
|
||||
componentId: this.fixComponentId(componentId),
|
||||
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;
|
||||
// Store session.
|
||||
this.login(siteId);
|
||||
this.eventsProvider.trigger(CoreEventsProvider.SITE_ADDED, siteId);
|
||||
this.eventsProvider.trigger(CoreEventsProvider.SITE_ADDED, {}, siteId);
|
||||
|
||||
if (this.siteTablesSchemas.length) {
|
||||
// Create tables in the site's database.
|
||||
|
@ -558,7 +558,7 @@ export class CoreSitesProvider {
|
|||
// Check if local_mobile was installed to Moodle.
|
||||
return site.checkIfLocalMobileInstalledAndNotUsed().then(() => {
|
||||
// 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.
|
||||
this.updateSiteInfo(siteId);
|
||||
|
@ -622,7 +622,7 @@ export class CoreSitesProvider {
|
|||
// DB remove shouldn't fail, but we'll go ahead even if it does.
|
||||
return site.deleteFolder();
|
||||
}).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).
|
||||
* @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 site.getSiteHomeId();
|
||||
});
|
||||
|
@ -801,7 +801,7 @@ export class CoreSitesProvider {
|
|||
siteId: siteId
|
||||
};
|
||||
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}));
|
||||
|
||||
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(() => {
|
||||
this.eventsProvider.trigger(CoreEventsProvider.SITE_UPDATED, {siteId: siteId});
|
||||
this.eventsProvider.trigger(CoreEventsProvider.SITE_UPDATED, {}, siteId);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -323,7 +323,7 @@ export class CoreMimetypeUtilsProvider {
|
|||
let filename = '',
|
||||
mimetype = '',
|
||||
extension = '',
|
||||
langPrefix = 'core.mimetype-';
|
||||
langPrefix = 'assets.mimetypes.';
|
||||
|
||||
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.
|
||||
|
@ -422,7 +422,7 @@ export class CoreMimetypeUtilsProvider {
|
|||
* @return {string} Translated name.
|
||||
*/
|
||||
getTranslatedGroupName(name: string) : string {
|
||||
let key = 'core.mimetype-group:' + name,
|
||||
let key = 'assets.mimetypes.group:' + name,
|
||||
translated = this.translate.instant(key);
|
||||
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.
|
||||
*/
|
||||
getCountryName(code: string) : string {
|
||||
let countryKey = 'core.country-' + code,
|
||||
let countryKey = 'assets.countries.' + code,
|
||||
countryName = this.translate.instant(countryKey);
|
||||
|
||||
return countryName !== countryKey ? countryName : code;
|
||||
|
@ -580,8 +580,8 @@ export class CoreUtilsProvider {
|
|||
let countries = {};
|
||||
|
||||
for (let name in table) {
|
||||
if (name.indexOf('core.country-') === 0) {
|
||||
let code = name.replace('core.country-', '');
|
||||
if (name.indexOf('assets.countries.') === 0) {
|
||||
let code = name.replace('assets.countries.', '');
|
||||
countries[code] = table[name];
|
||||
}
|
||||
}
|
||||
|
|
10
tslint.json
10
tslint.json
|
@ -3,7 +3,15 @@
|
|||
"no-duplicate-variable": true,
|
||||
"no-unused-variable": [
|
||||
true
|
||||
]
|
||||
],
|
||||
"max-line-length": {
|
||||
"options": [132]
|
||||
},
|
||||
},
|
||||
"jsRules": {
|
||||
"max-line-length": {
|
||||
"options": [132]
|
||||
}
|
||||
},
|
||||
"rulesDirectory": [
|
||||
"node_modules/tslint-eslint-rules/dist/rules"
|
||||
|
|
Loading…
Reference in New Issue