commit
1610018c4d
49
gulpfile.js
49
gulpfile.js
|
@ -6,7 +6,21 @@ var gulp = require('gulp'),
|
|||
slash = require('gulp-slash'),
|
||||
clipEmptyFiles = require('gulp-clip-empty-files'),
|
||||
gutil = require('gulp-util'),
|
||||
File = gutil.File;
|
||||
File = gutil.File,
|
||||
license = '' +
|
||||
'// (C) Copyright 2015 Martin Dougiamas\n' +
|
||||
'//\n' +
|
||||
'// Licensed under the Apache License, Version 2.0 (the "License");\n' +
|
||||
'// you may not use this file except in compliance with the License.\n' +
|
||||
'// You may obtain a copy of the License at\n' +
|
||||
'//\n' +
|
||||
'// http://www.apache.org/licenses/LICENSE-2.0\n' +
|
||||
'//\n' +
|
||||
'// Unless required by applicable law or agreed to in writing, software\n' +
|
||||
'// distributed under the License is distributed on an "AS IS" BASIS,\n' +
|
||||
'// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n' +
|
||||
'// See the License for the specific language governing permissions and\n' +
|
||||
'// limitations under the License.\n\n';
|
||||
|
||||
// Get the names of the JSON files inside a directory.
|
||||
function getFilenames(dir) {
|
||||
|
@ -211,15 +225,40 @@ gulp.task('config', function(done) {
|
|||
gulp.src(paths.config)
|
||||
.pipe(through(function(file) {
|
||||
// Convert the contents of the file into a TypeScript class.
|
||||
// Disable the rule variable-name in the file.
|
||||
var config = JSON.parse(file.contents.toString()),
|
||||
contents = 'export class CoreConfigConstants {\n';
|
||||
contents = license + '// tslint:disable: variable-name\n' + 'export class CoreConfigConstants {\n';
|
||||
|
||||
for (var key in config) {
|
||||
var value = config[key];
|
||||
if (typeof value != 'number' && typeof value != 'boolean') {
|
||||
value = JSON.stringify(value);
|
||||
if (typeof value == 'string') {
|
||||
// Wrap the string in ' .
|
||||
value = "'" + value + "'";
|
||||
} else if (typeof value != 'number' && typeof value != 'boolean') {
|
||||
// Stringify with 4 spaces of indentation, and then add 4 more spaces in each line.
|
||||
value = JSON.stringify(value, null, 4).replace(/^(?: )/gm, ' ').replace(/^(?:})/gm, ' }');
|
||||
// Replace " by ' in values.
|
||||
value = value.replace(/: "([^"]*)"/g, ": '$1'");
|
||||
|
||||
// Check if the keys have "-" in it.
|
||||
var matches = value.match(/"([^"]*\-[^"]*)":/g);
|
||||
if (matches) {
|
||||
// Replace " by ' in keys. We cannot remove them because keys have chars like '-'.
|
||||
value = value.replace(/"([^"]*)":/g, "'$1':");
|
||||
} else {
|
||||
// Remove ' in keys.
|
||||
value = value.replace(/"([^"]*)":/g, "$1:");
|
||||
}
|
||||
|
||||
// Add type any to the key.
|
||||
key = key + ': any';
|
||||
}
|
||||
contents += ' public static ' + key + ' = ' + value + ';\n';
|
||||
|
||||
// If key has quotation marks, remove them.
|
||||
if (key[0] == '"') {
|
||||
key = key.substr(1, key.length - 2);
|
||||
}
|
||||
contents += ' static ' + key + ' = ' + value + ';\n';
|
||||
}
|
||||
contents += '}\n';
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { AddonCalendarProvider } from './providers/calendar';
|
||||
import { AddonCalendarHelperProvider } from './providers/helper';
|
||||
import { AddonCalendarMainMenuHandler } from './providers/handlers';
|
||||
import { AddonCalendarMainMenuHandler } from './providers/mainmenu-handler';
|
||||
import { CoreMainMenuDelegate } from '../../core/mainmenu/providers/delegate';
|
||||
import { CoreInitDelegate } from '../../providers/init';
|
||||
import { CoreLocalNotificationsProvider } from '../../providers/local-notifications';
|
||||
|
@ -42,11 +42,10 @@ export class AddonCalendarModule {
|
|||
calendarProvider.scheduleAllSitesEventsNotifications();
|
||||
});
|
||||
|
||||
|
||||
localNotificationsProvider.registerClick(AddonCalendarProvider.COMPONENT, (data) => {
|
||||
if (data.eventid) {
|
||||
initDelegate.ready().then(() => {
|
||||
calendarProvider.isDisabled(data.siteId).then(function(disabled) {
|
||||
calendarProvider.isDisabled(data.siteId).then((disabled) => {
|
||||
if (disabled) {
|
||||
// The calendar is disabled in the site, don't open it.
|
||||
return;
|
||||
|
@ -58,4 +57,4 @@ export class AddonCalendarModule {
|
|||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ import * as moment from 'moment';
|
|||
/**
|
||||
* Page that displays a single calendar event.
|
||||
*/
|
||||
@IonicPage({segment: "addon-calendar-event"})
|
||||
@IonicPage({ segment: 'addon-calendar-event' })
|
||||
@Component({
|
||||
selector: 'page-addon-calendar-event',
|
||||
templateUrl: 'event.html',
|
||||
|
@ -45,10 +45,10 @@ export class AddonCalendarEventPage {
|
|||
courseName: string;
|
||||
notificationsEnabled = false;
|
||||
|
||||
constructor(private translate: TranslateService, private calendarProvider: AddonCalendarProvider, private navParams: NavParams,
|
||||
constructor(private translate: TranslateService, private calendarProvider: AddonCalendarProvider, navParams: NavParams,
|
||||
private domUtils: CoreDomUtilsProvider, private coursesProvider: CoreCoursesProvider,
|
||||
private calendarHelper: AddonCalendarHelperProvider, private sitesProvider: CoreSitesProvider,
|
||||
private localNotificationsProvider: CoreLocalNotificationsProvider, private courseProvider: CoreCourseProvider) {
|
||||
private calendarHelper: AddonCalendarHelperProvider, sitesProvider: CoreSitesProvider,
|
||||
localNotificationsProvider: CoreLocalNotificationsProvider, private courseProvider: CoreCourseProvider) {
|
||||
|
||||
this.eventId = navParams.get('id');
|
||||
this.notificationsEnabled = localNotificationsProvider.isAvailable();
|
||||
|
@ -72,13 +72,13 @@ export class AddonCalendarEventPage {
|
|||
/**
|
||||
* View loaded.
|
||||
*/
|
||||
ionViewDidLoad() {
|
||||
ionViewDidLoad(): void {
|
||||
this.fetchEvent().finally(() => {
|
||||
this.eventLoaded = true;
|
||||
});
|
||||
}
|
||||
|
||||
updateNotificationTime() {
|
||||
updateNotificationTime(): void {
|
||||
if (!isNaN(this.notificationTime) && this.event && this.event.id) {
|
||||
this.calendarProvider.updateNotificationTime(this.event, this.notificationTime);
|
||||
}
|
||||
|
@ -86,8 +86,10 @@ export class AddonCalendarEventPage {
|
|||
|
||||
/**
|
||||
* Fetches the event and updates the view.
|
||||
*
|
||||
* @return {Promise<any>} Promise resolved when done.
|
||||
*/
|
||||
fetchEvent() {
|
||||
fetchEvent(): Promise<any> {
|
||||
return this.calendarProvider.getEvent(this.eventId).then((event) => {
|
||||
this.calendarHelper.formatEventData(event);
|
||||
this.event = event;
|
||||
|
@ -96,14 +98,14 @@ export class AddonCalendarEventPage {
|
|||
let title = this.translate.instant('addon.calendar.type' + event.eventtype);
|
||||
if (event.moduleIcon) {
|
||||
// It's a module event, translate the module name to the current language.
|
||||
let name = this.courseProvider.translateModuleName(event.modulename);
|
||||
const 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);
|
||||
title = this.translate.instant('core.mod_' + event.modulename + '.' + event.eventtype);
|
||||
|
||||
if (title == 'core.mod_'+ event.modulename + '.' + event.eventtype) {
|
||||
if (title == 'core.mod_' + event.modulename + '.' + event.eventtype) {
|
||||
title = name;
|
||||
}
|
||||
}
|
||||
|
@ -130,11 +132,11 @@ export class AddonCalendarEventPage {
|
|||
*
|
||||
* @param {any} refresher Refresher.
|
||||
*/
|
||||
refreshEvent(refresher: any) {
|
||||
refreshEvent(refresher: any): void {
|
||||
this.calendarProvider.invalidateEvent(this.eventId).finally(() => {
|
||||
this.fetchEvent().finally(() => {
|
||||
refresher.complete();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ import { CoreSplitViewComponent } from '../../../../components/split-view/split-
|
|||
/**
|
||||
* Page that displays the list of calendar events.
|
||||
*/
|
||||
@IonicPage({segment: "addon-calendar-list"})
|
||||
@IonicPage({ segment: 'addon-calendar-list' })
|
||||
@Component({
|
||||
selector: 'page-addon-calendar-list',
|
||||
templateUrl: 'list.html',
|
||||
|
@ -63,11 +63,11 @@ export class AddonCalendarListPage implements OnDestroy {
|
|||
course: this.allCourses
|
||||
};
|
||||
|
||||
constructor(private translate: TranslateService, private calendarProvider: AddonCalendarProvider, private navParams: NavParams,
|
||||
constructor(private translate: TranslateService, private calendarProvider: AddonCalendarProvider, 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) {
|
||||
private calendarHelper: AddonCalendarHelperProvider, sitesProvider: CoreSitesProvider,
|
||||
localNotificationsProvider: CoreLocalNotificationsProvider, private popoverCtrl: PopoverController,
|
||||
eventsProvider: CoreEventsProvider, private navCtrl: NavController, appProvider: CoreAppProvider) {
|
||||
|
||||
this.siteHomeId = sitesProvider.getCurrentSite().getSiteHomeId();
|
||||
this.notificationsEnabled = localNotificationsProvider.isAvailable();
|
||||
|
@ -84,7 +84,7 @@ export class AddonCalendarListPage implements OnDestroy {
|
|||
/**
|
||||
* View loaded.
|
||||
*/
|
||||
ionViewDidLoad() {
|
||||
ionViewDidLoad(): void {
|
||||
if (this.eventId) {
|
||||
// There is an event to load, open the event in a new state.
|
||||
this.gotoEvent(this.eventId);
|
||||
|
@ -104,8 +104,9 @@ export class AddonCalendarListPage implements OnDestroy {
|
|||
* Fetch all the data required for the view.
|
||||
*
|
||||
* @param {boolean} [refresh] Empty events array first.
|
||||
* @return {Promise<any>} Promise resolved when done.
|
||||
*/
|
||||
fetchData(refresh = false) {
|
||||
fetchData(refresh: boolean = false): Promise<any> {
|
||||
this.daysLoaded = 0;
|
||||
this.emptyEventsTimes = 0;
|
||||
|
||||
|
@ -114,6 +115,7 @@ export class AddonCalendarListPage implements OnDestroy {
|
|||
// Add "All courses".
|
||||
courses.unshift(this.allCourses);
|
||||
this.courses = courses;
|
||||
|
||||
return this.fetchEvents(refresh);
|
||||
});
|
||||
}
|
||||
|
@ -122,8 +124,9 @@ export class AddonCalendarListPage implements OnDestroy {
|
|||
* Fetches the events and updates the view.
|
||||
*
|
||||
* @param {boolean} [refresh] Empty events array first.
|
||||
* @return {Promise<any>} Promise resolved when done.
|
||||
*/
|
||||
fetchEvents(refresh = false) {
|
||||
fetchEvents(refresh: boolean = false): Promise<any> {
|
||||
return this.calendarProvider.getEventsList(this.daysLoaded, AddonCalendarProvider.DAYS_INTERVAL).then((events) => {
|
||||
this.daysLoaded += AddonCalendarProvider.DAYS_INTERVAL;
|
||||
if (events.length === 0) {
|
||||
|
@ -170,6 +173,7 @@ export class AddonCalendarListPage implements OnDestroy {
|
|||
// Success retrieving events. Get categories if needed.
|
||||
if (this.getCategories) {
|
||||
this.getCategories = false;
|
||||
|
||||
return this.loadCategories();
|
||||
}
|
||||
});
|
||||
|
@ -177,8 +181,10 @@ export class AddonCalendarListPage implements OnDestroy {
|
|||
|
||||
/**
|
||||
* Get filtered events.
|
||||
*
|
||||
* @return {any[]} Filtered events.
|
||||
*/
|
||||
protected getFilteredEvents() {
|
||||
protected getFilteredEvents(): any[] {
|
||||
if (this.filter.course.id == -1) {
|
||||
// No filter, display everything.
|
||||
return this.events;
|
||||
|
@ -191,8 +197,9 @@ export class AddonCalendarListPage implements OnDestroy {
|
|||
* Check if an event should be displayed based on the filter.
|
||||
*
|
||||
* @param {any} event Event object.
|
||||
* @return {boolean} Whether it should be displayed.
|
||||
*/
|
||||
protected shouldDisplayEvent(event: any) {
|
||||
protected shouldDisplayEvent(event: any): boolean {
|
||||
if (event.eventtype == 'user' || event.eventtype == 'site') {
|
||||
// User or site event, display it.
|
||||
return true;
|
||||
|
@ -234,21 +241,24 @@ export class AddonCalendarListPage implements OnDestroy {
|
|||
* @param {any[]} events Events to parse.
|
||||
* @return {boolean} True if categories should be loaded.
|
||||
*/
|
||||
protected shouldLoadCategories(events: any[]) : boolean {
|
||||
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);
|
||||
const found = events.some((event) => event.categoryid != 'undefined' && event.categoryid > 0);
|
||||
|
||||
return found || this.getCategories;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load categories to be able to filter events.
|
||||
*
|
||||
* @return {Promise<any>} Promise resolved when done.
|
||||
*/
|
||||
protected loadCategories() {
|
||||
protected loadCategories(): Promise<any> {
|
||||
return this.coursesProvider.getCategories(0, true).then((cats) => {
|
||||
this.categoriesRetrieved = true;
|
||||
this.categories = {};
|
||||
|
@ -266,8 +276,8 @@ export class AddonCalendarListPage implements OnDestroy {
|
|||
*
|
||||
* @param {any} refresher Refresher.
|
||||
*/
|
||||
refreshEvents(refresher: any) {
|
||||
let promises = [];
|
||||
refreshEvents(refresher: any): void {
|
||||
const promises = [];
|
||||
|
||||
promises.push(this.calendarProvider.invalidateEventsList(this.courses));
|
||||
|
||||
|
@ -288,9 +298,11 @@ export class AddonCalendarListPage implements OnDestroy {
|
|||
*
|
||||
* @param {MouseEvent} event Event.
|
||||
*/
|
||||
openCourseFilter(event: MouseEvent) : void {
|
||||
let popover = this.popoverCtrl.create(CoreCoursePickerMenuPopoverComponent, {courses: this.courses,
|
||||
courseId: this.filter.course.id});
|
||||
openCourseFilter(event: MouseEvent): void {
|
||||
const popover = this.popoverCtrl.create(CoreCoursePickerMenuPopoverComponent, {
|
||||
courses: this.courses,
|
||||
courseId: this.filter.course.id
|
||||
});
|
||||
popover.onDidDismiss((course) => {
|
||||
if (course) {
|
||||
this.filter.course = course;
|
||||
|
@ -306,22 +318,24 @@ export class AddonCalendarListPage implements OnDestroy {
|
|||
/**
|
||||
* Open calendar events settings.
|
||||
*/
|
||||
openSettings() {
|
||||
openSettings(): void {
|
||||
this.navCtrl.push('AddonCalendarSettingsPage');
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to a particular event.
|
||||
*
|
||||
* @param {number} eventId Event to load.
|
||||
*/
|
||||
gotoEvent(eventId) {
|
||||
gotoEvent(eventId: number): void {
|
||||
this.eventId = eventId;
|
||||
this.splitviewCtrl.push('AddonCalendarEventPage', {id: eventId});
|
||||
this.splitviewCtrl.push('AddonCalendarEventPage', { id: eventId });
|
||||
}
|
||||
|
||||
/**
|
||||
* Page destroyed.
|
||||
*/
|
||||
ngOnDestroy() {
|
||||
ngOnDestroy(): void {
|
||||
this.obsDefaultTimeChange && this.obsDefaultTimeChange.off();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ import { CoreSitesProvider } from '../../../../providers/sites';
|
|||
/**
|
||||
* Page that displays the calendar settings.
|
||||
*/
|
||||
@IonicPage({segment: "addon-calendar-settings"})
|
||||
@IonicPage({ segment: 'addon-calendar-settings' })
|
||||
@Component({
|
||||
selector: 'page-addon-calendar-settings',
|
||||
templateUrl: 'settings.html',
|
||||
|
@ -31,20 +31,25 @@ export class AddonCalendarSettingsPage {
|
|||
defaultTime = 0;
|
||||
|
||||
constructor(private calendarProvider: AddonCalendarProvider, private eventsProvider: CoreEventsProvider,
|
||||
private sitesProvider: CoreSitesProvider) {}
|
||||
private sitesProvider: CoreSitesProvider) { }
|
||||
|
||||
/**
|
||||
* View loaded.
|
||||
*/
|
||||
ionViewDidLoad() {
|
||||
ionViewDidLoad(): void {
|
||||
this.calendarProvider.getDefaultNotificationTime().then((time) => {
|
||||
this.defaultTime = time;
|
||||
});
|
||||
}
|
||||
|
||||
updateDefaultTime(newTime) {
|
||||
/**
|
||||
* Update default time.
|
||||
*
|
||||
* @param {number} newTime New time.
|
||||
*/
|
||||
updateDefaultTime(newTime: number): void {
|
||||
this.calendarProvider.setDefaultNotificationTime(newTime);
|
||||
this.eventsProvider.trigger(AddonCalendarProvider.DEFAULT_NOTIFICATION_TIME_CHANGED, {time: newTime},
|
||||
this.eventsProvider.trigger(AddonCalendarProvider.DEFAULT_NOTIFICATION_TIME_CHANGED, { time: newTime },
|
||||
this.sitesProvider.getCurrentSiteId());
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,9 +28,9 @@ import { CoreConfigProvider } from '../../../providers/config';
|
|||
*/
|
||||
@Injectable()
|
||||
export class AddonCalendarProvider {
|
||||
public static DAYS_INTERVAL = 30;
|
||||
public static COMPONENT = 'AddonCalendarEvents';
|
||||
public static DEFAULT_NOTIFICATION_TIME_CHANGED = 'AddonCalendarDefaultNotificationTimeChangedEvent';
|
||||
static DAYS_INTERVAL = 30;
|
||||
static COMPONENT = 'AddonCalendarEvents';
|
||||
static DEFAULT_NOTIFICATION_TIME_CHANGED = 'AddonCalendarDefaultNotificationTimeChangedEvent';
|
||||
protected DEFAULT_NOTIFICATION_TIME_SETTING = 'mmaCalendarDefaultNotifTime';
|
||||
protected ROOT_CACHE_KEY = 'mmaCalendar:';
|
||||
protected DEFAULT_NOTIFICATION_TIME = 60;
|
||||
|
@ -118,10 +118,11 @@ export class AddonCalendarProvider {
|
|||
* @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> {
|
||||
getDefaultNotificationTime(siteId?: string): Promise<number> {
|
||||
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||
|
||||
let key = this.DEFAULT_NOTIFICATION_TIME_SETTING + '#' + siteId;
|
||||
const key = this.DEFAULT_NOTIFICATION_TIME_SETTING + '#' + siteId;
|
||||
|
||||
return this.configProvider.get(key, this.DEFAULT_NOTIFICATION_TIME);
|
||||
}
|
||||
|
||||
|
@ -133,19 +134,27 @@ export class AddonCalendarProvider {
|
|||
* @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> {
|
||||
getEvent(id: number, siteId?: string): Promise<any> {
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
let presets = {
|
||||
const preSets = {
|
||||
cacheKey: this.getEventCacheKey(id)
|
||||
},
|
||||
data = {
|
||||
"options[userevents]": 0,
|
||||
"options[siteevents]": 0,
|
||||
"events[eventids][0]": id
|
||||
options: {
|
||||
userevents: 0,
|
||||
siteevents: 0,
|
||||
},
|
||||
events: {
|
||||
eventids: [
|
||||
id
|
||||
]
|
||||
}
|
||||
};
|
||||
return site.read('core_calendar_get_calendar_events', data, presets).then((response) => {
|
||||
|
||||
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});
|
||||
const event = response.events.find((e) => { return e.id == id; });
|
||||
|
||||
return event || this.getEventFromLocalDb(id);
|
||||
}).catch(() => {
|
||||
return this.getEventFromLocalDb(id);
|
||||
|
@ -170,9 +179,9 @@ export class AddonCalendarProvider {
|
|||
* @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> {
|
||||
getEventFromLocalDb(id: number, siteId?: string): Promise<any> {
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
return site.getDb().getRecord(this.EVENTS_TABLE, {id: id});
|
||||
return site.getDb().getRecord(this.EVENTS_TABLE, { id: id });
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -183,13 +192,14 @@ export class AddonCalendarProvider {
|
|||
* @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> {
|
||||
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;
|
||||
});
|
||||
}
|
||||
|
@ -201,7 +211,7 @@ export class AddonCalendarProvider {
|
|||
* @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> {
|
||||
getEventNotificationTimeOption(id: number, siteId?: string): Promise<number> {
|
||||
return this.getEventFromLocalDb(id, siteId).then((e) => {
|
||||
return e.notificationtime || -1;
|
||||
}).catch(() => {
|
||||
|
@ -221,42 +231,48 @@ export class AddonCalendarProvider {
|
|||
* @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[]> {
|
||||
getEventsList(daysToStart: number = 0, daysInterval: number = 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.
|
||||
courses.push({ id: site.getSiteHomeId() }); // Add front page.
|
||||
|
||||
return this.groupsProvider.getUserGroups(courses, siteId).then((groups) => {
|
||||
let now = this.timeUtils.timestamp(),
|
||||
const now = this.timeUtils.timestamp(),
|
||||
start = now + (CoreConstants.SECONDS_DAY * daysToStart),
|
||||
end = start + (CoreConstants.SECONDS_DAY * 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
|
||||
};
|
||||
end = start + (CoreConstants.SECONDS_DAY * daysInterval),
|
||||
data = {
|
||||
options: {
|
||||
userevents: 1,
|
||||
siteevents: 1,
|
||||
timestart: start,
|
||||
timeend: end
|
||||
},
|
||||
events: {
|
||||
courseids: [],
|
||||
groupids: []
|
||||
}
|
||||
};
|
||||
|
||||
courses.forEach((course, index) => {
|
||||
data["events[courseids][" + index + "]"] = course.id;
|
||||
data.events.courseids[index] = course.id;
|
||||
});
|
||||
|
||||
groups.forEach((group, index) => {
|
||||
data["events[groupids][" + index + "]"] = group.id;
|
||||
data.events.groupids[index] = group.id;
|
||||
});
|
||||
|
||||
// We need to retrieve cached data using cache key because we have timestamp in the params.
|
||||
let preSets = {
|
||||
const 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;
|
||||
});
|
||||
});
|
||||
|
@ -269,7 +285,7 @@ export class AddonCalendarProvider {
|
|||
*
|
||||
* @return {string} Prefix Cache key.
|
||||
*/
|
||||
protected getEventsListPrefixCacheKey() : string {
|
||||
protected getEventsListPrefixCacheKey(): string {
|
||||
return this.ROOT_CACHE_KEY + 'eventslist:';
|
||||
}
|
||||
|
||||
|
@ -280,7 +296,7 @@ export class AddonCalendarProvider {
|
|||
* @param {number} daysInterval Number of days between timestart and timeend.
|
||||
* @return {string} Cache key.
|
||||
*/
|
||||
protected getEventsListCacheKey(daysToStart: number, daysInterval: number) : string {
|
||||
protected getEventsListCacheKey(daysToStart: number, daysInterval: number): string {
|
||||
return this.getEventsListPrefixCacheKey() + daysToStart + ':' + daysInterval;
|
||||
}
|
||||
|
||||
|
@ -291,27 +307,28 @@ export class AddonCalendarProvider {
|
|||
* @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[]> {
|
||||
invalidateEventsList(courses: any[], siteId?: string): Promise<any[]> {
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
siteId = site.getId();
|
||||
|
||||
let promises = [];
|
||||
const 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> {
|
||||
invalidateEvent(eventId: number, siteId?: string): Promise<any> {
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
return site.invalidateWsCacheForKey(this.getEventCacheKey(eventId));
|
||||
});
|
||||
|
@ -323,8 +340,9 @@ export class AddonCalendarProvider {
|
|||
* @param {CoreSite} [site] Site. If not defined, use current site.
|
||||
* @return {boolean} Whether it's disabled.
|
||||
*/
|
||||
isCalendarDisabledInSite(site?: CoreSite) : boolean {
|
||||
isCalendarDisabledInSite(site?: CoreSite): boolean {
|
||||
site = site || this.sitesProvider.getCurrentSite();
|
||||
|
||||
return site.isFeatureDisabled('$mmSideMenuDelegate_mmaCalendar');
|
||||
}
|
||||
|
||||
|
@ -334,13 +352,12 @@ export class AddonCalendarProvider {
|
|||
* @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> {
|
||||
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).
|
||||
|
@ -348,10 +365,10 @@ export class AddonCalendarProvider {
|
|||
*
|
||||
* @return {Promise} Promise resolved when all the notifications have been scheduled.
|
||||
*/
|
||||
scheduleAllSitesEventsNotifications() : Promise<any[]> {
|
||||
scheduleAllSitesEventsNotifications(): Promise<any[]> {
|
||||
if (this.localNotificationsProvider.isAvailable()) {
|
||||
return this.sitesProvider.getSitesIds().then((siteIds) => {
|
||||
let promises = [];
|
||||
const promises = [];
|
||||
|
||||
siteIds.forEach((siteId) => {
|
||||
// Check if calendar is disabled for the site.
|
||||
|
@ -381,7 +398,7 @@ export class AddonCalendarProvider {
|
|||
* @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> {
|
||||
scheduleEventNotification(event: any, time: number, siteId?: string): Promise<void> {
|
||||
if (this.localNotificationsProvider.isAvailable()) {
|
||||
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||
|
||||
|
@ -391,16 +408,16 @@ export class AddonCalendarProvider {
|
|||
}
|
||||
|
||||
// If time is -1, get event default time.
|
||||
let promise = time == -1 ? this.getDefaultNotificationTime(siteId) : Promise.resolve(time);
|
||||
const promise = time == -1 ? this.getDefaultNotificationTime(siteId) : Promise.resolve(time);
|
||||
|
||||
return promise.then((time) => {
|
||||
let timeEnd = (event.timestart + event.timeduration) * 1000;
|
||||
const 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),
|
||||
const dateTriggered = new Date((event.timestart - (time * 60)) * 1000),
|
||||
startDate = new Date(event.timestart * 1000),
|
||||
notification = {
|
||||
id: event.id,
|
||||
|
@ -421,7 +438,6 @@ export class AddonCalendarProvider {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Schedules the notifications for a list of events.
|
||||
* If an event notification time is 0, cancel its scheduled notification (if any).
|
||||
|
@ -431,8 +447,8 @@ export class AddonCalendarProvider {
|
|||
* @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 = [];
|
||||
scheduleEventsNotifications(events: any[], siteId?: string): Promise<any[]> {
|
||||
const promises = [];
|
||||
|
||||
if (this.localNotificationsProvider.isAvailable()) {
|
||||
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||
|
@ -453,10 +469,11 @@ export class AddonCalendarProvider {
|
|||
* @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[]> {
|
||||
setDefaultNotificationTime(time: number, siteId?: string): Promise<any[]> {
|
||||
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||
|
||||
let key = this.DEFAULT_NOTIFICATION_TIME_SETTING + '#' + siteId;
|
||||
const key = this.DEFAULT_NOTIFICATION_TIME_SETTING + '#' + siteId;
|
||||
|
||||
return this.configProvider.set(key, time);
|
||||
}
|
||||
|
||||
|
@ -467,11 +484,11 @@ export class AddonCalendarProvider {
|
|||
* @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[]> {
|
||||
protected storeEventsInLocalDB(events: any[], siteId?: string): Promise<any[]> {
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
siteId = site.getId();
|
||||
|
||||
let promises = [],
|
||||
const promises = [],
|
||||
db = site.getDb();
|
||||
|
||||
events.forEach((event) => {
|
||||
|
@ -480,7 +497,7 @@ export class AddonCalendarProvider {
|
|||
// Event not stored, return empty object.
|
||||
return {};
|
||||
}).then((e) => {
|
||||
let eventRecord = {
|
||||
const eventRecord = {
|
||||
id: event.id,
|
||||
name: event.name,
|
||||
description: event.description,
|
||||
|
@ -497,7 +514,7 @@ export class AddonCalendarProvider {
|
|||
notificationtime: e.notificationtime || -1
|
||||
};
|
||||
|
||||
return db.insertOrUpdateRecord(this.EVENTS_TABLE, eventRecord, {id: eventRecord.id});
|
||||
return db.insertOrUpdateRecord(this.EVENTS_TABLE, eventRecord, { id: eventRecord.id });
|
||||
}));
|
||||
});
|
||||
|
||||
|
@ -513,7 +530,7 @@ export class AddonCalendarProvider {
|
|||
* @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> {
|
||||
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.
|
||||
|
@ -522,7 +539,7 @@ export class AddonCalendarProvider {
|
|||
|
||||
event.notificationtime = time;
|
||||
|
||||
return site.getDb().insertOrUpdateRecord(this.EVENTS_TABLE, event, {id: event.id}).then(() => {
|
||||
return site.getDb().insertOrUpdateRecord(this.EVENTS_TABLE, event, { id: event.id }).then(() => {
|
||||
return this.scheduleEventNotification(event, time);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CoreLoggerProvider } from '../../../providers/logger';
|
||||
import { CoreSitesProvider } from '../../../providers/sites';
|
||||
import { CoreCourseProvider } from '../../../core/course/providers/course';
|
||||
|
||||
/**
|
||||
|
@ -24,15 +23,15 @@ import { CoreCourseProvider } from '../../../core/course/providers/course';
|
|||
export class AddonCalendarHelperProvider {
|
||||
protected logger;
|
||||
|
||||
private EVENTICONS = {
|
||||
'course': 'ionic',
|
||||
'group': 'people',
|
||||
'site': 'globe',
|
||||
'user': 'person',
|
||||
'category': 'albums'
|
||||
protected EVENTICONS = {
|
||||
course: 'ionic',
|
||||
group: 'people',
|
||||
site: 'globe',
|
||||
user: 'person',
|
||||
category: 'albums'
|
||||
};
|
||||
|
||||
constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private courseProvider: CoreCourseProvider) {
|
||||
constructor(logger: CoreLoggerProvider, private courseProvider: CoreCourseProvider) {
|
||||
this.logger = logger.getInstance('AddonCalendarHelperProvider');
|
||||
}
|
||||
|
||||
|
@ -41,11 +40,11 @@ export class AddonCalendarHelperProvider {
|
|||
*
|
||||
* @param {any} e Event to format.
|
||||
*/
|
||||
formatEventData(e: any) {
|
||||
formatEventData(e: any): void {
|
||||
e.icon = this.EVENTICONS[e.eventtype] || false;
|
||||
if (!e.icon) {
|
||||
e.icon = this.courseProvider.getModuleIconSrc(e.modulename);
|
||||
e.moduleIcon = e.icon;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,16 +24,15 @@ export class AddonCalendarMainMenuHandler implements CoreMainMenuHandler {
|
|||
name = 'AddonCalendar';
|
||||
priority = 400;
|
||||
|
||||
constructor(private calendarProvider: AddonCalendarProvider) {}
|
||||
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;
|
||||
isEnabled(): boolean | Promise<boolean> {
|
||||
return !this.calendarProvider.isCalendarDisabledInSite();
|
||||
}
|
||||
|
||||
/**
|
|
@ -0,0 +1,46 @@
|
|||
// (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 { IonicModule } from 'ionic-angular';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { AddonUserProfileFieldCheckboxHandler } from './providers/handler';
|
||||
import { CoreUserProfileFieldDelegate } from '../../../core/user/providers/user-profile-field-delegate';
|
||||
import { AddonUserProfileFieldCheckboxComponent } from './component/checkbox';
|
||||
import { CoreComponentsModule } from '../../../components/components.module';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AddonUserProfileFieldCheckboxComponent
|
||||
],
|
||||
imports: [
|
||||
IonicModule,
|
||||
TranslateModule.forChild(),
|
||||
CoreComponentsModule
|
||||
],
|
||||
providers: [
|
||||
AddonUserProfileFieldCheckboxHandler
|
||||
],
|
||||
exports: [
|
||||
AddonUserProfileFieldCheckboxComponent
|
||||
],
|
||||
entryComponents: [
|
||||
AddonUserProfileFieldCheckboxComponent
|
||||
]
|
||||
})
|
||||
export class AddonUserProfileFieldCheckboxModule {
|
||||
constructor(userProfileFieldDelegate: CoreUserProfileFieldDelegate, handler: AddonUserProfileFieldCheckboxHandler) {
|
||||
userProfileFieldDelegate.registerHandler(handler);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
<!-- Render (no edit). -->
|
||||
<ion-item *ngIf="!edit && field && field.name">
|
||||
<h2>{{ field.name }}</h2>
|
||||
<p *ngIf="field.value != '0'">
|
||||
{{ 'core.yes' | translate }}
|
||||
</p>
|
||||
<p *ngIf="field.value == '0'">
|
||||
{{ 'core.no' | translate }}
|
||||
</p>
|
||||
</ion-item>
|
||||
<!-- Edit. -->
|
||||
<ion-item *ngIf="edit && field && field.shortname" [formGroup]="form">
|
||||
<ion-label [core-mark-required]="field.required">{{ field.name }}</ion-label>
|
||||
<ion-checkbox item-end [formControlName]="field.modelName">
|
||||
</ion-checkbox>
|
||||
<core-input-errors [control]="form.controls[field.modelName]" [errorMessages]="errors"></core-input-errors>
|
||||
</ion-item>
|
|
@ -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 { Component, Input, OnInit } from '@angular/core';
|
||||
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
|
||||
import { CoreUtilsProvider } from '../../../../providers/utils/utils';
|
||||
|
||||
/**
|
||||
* Directive to render a checkbox user profile field.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'addon-user-profile-field-checkbox',
|
||||
templateUrl: 'checkbox.html'
|
||||
})
|
||||
export class AddonUserProfileFieldCheckboxComponent implements OnInit {
|
||||
@Input() field: any; // The profile field to be rendered.
|
||||
@Input() edit?: false; // True if editing the field. Defaults to false.
|
||||
@Input() disabled?: false; // True if disabled. Defaults to false.
|
||||
@Input() form?: FormGroup; // Form where to add the form control.
|
||||
|
||||
constructor(private fb: FormBuilder, protected utils: CoreUtilsProvider) { }
|
||||
|
||||
/**
|
||||
* Component being initialized.
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
const field = this.field;
|
||||
|
||||
if (field && this.edit && this.form) {
|
||||
field.modelName = 'profile_field_' + field.shortname;
|
||||
|
||||
// Initialize the value.
|
||||
const formData = {
|
||||
value: this.utils.isTrueOrOne(field.defaultdata),
|
||||
disabled: this.disabled
|
||||
};
|
||||
this.form.addControl(field.modelName, this.fb.control(formData,
|
||||
field.required && !field.locked ? Validators.requiredTrue : null));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
|
||||
// (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 { CoreUserProfileFieldHandler, CoreUserProfileFieldHandlerData } from
|
||||
'../../../../core/user/providers/user-profile-field-delegate';
|
||||
import { AddonUserProfileFieldCheckboxComponent } from '../component/checkbox';
|
||||
|
||||
/**
|
||||
* Checkbox user profile field handlers.
|
||||
*/
|
||||
@Injectable()
|
||||
export class AddonUserProfileFieldCheckboxHandler implements CoreUserProfileFieldHandler {
|
||||
name = 'checkbox';
|
||||
|
||||
constructor() {
|
||||
// Nothing to do.
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not the handler is enabled on a site level.
|
||||
*
|
||||
* @return {boolean|Promise<boolean>} True or promise resolved with true if enabled.
|
||||
*/
|
||||
isEnabled(): boolean | Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the data to send for the field based on the input data.
|
||||
*
|
||||
* @param {any} field User field to get the data for.
|
||||
* @param {boolean} signup True if user is in signup page.
|
||||
* @param {string} [registerAuth] Register auth method. E.g. 'email'.
|
||||
* @param {any} formValues Form Values.
|
||||
* @return {CoreUserProfileFieldHandlerData} Data to send for the field.
|
||||
*/
|
||||
getData(field: any, signup: boolean, registerAuth: string, formValues: any): CoreUserProfileFieldHandlerData {
|
||||
const name = 'profile_field_' + field.shortname;
|
||||
|
||||
if (typeof formValues[name] != 'undefined') {
|
||||
return {
|
||||
type: 'checkbox',
|
||||
name: name,
|
||||
value: formValues[name] ? 1 : 0
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Component to use to display the user profile field.
|
||||
*
|
||||
* @return {any} The component to use, undefined if not found.
|
||||
*/
|
||||
getComponent(): any {
|
||||
return AddonUserProfileFieldCheckboxComponent;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
<!-- Render (no edit). -->
|
||||
<ion-item *ngIf="!edit && field && field.name">
|
||||
<h2>{{ field.name }}</h2>
|
||||
<p>{{ field.value * 1000 | coreFormatDate:"dfmediumdate"}}</p>
|
||||
</ion-item>
|
||||
<!-- Edit. -->
|
||||
<ion-item *ngIf="edit && field && field.shortname" text-wrap [formGroup]="form">
|
||||
<ion-label stacked [core-mark-required]="field.required">{{ field.name }}</ion-label>
|
||||
<ion-datetime [formControlName]="field.modelName" [placeholder]="'core.choosedots' | translate" [displayFormat]="field.format" core-input-errors [max]="field.max" [min]="field.min"></ion-datetime>
|
||||
</ion-item>
|
|
@ -0,0 +1,73 @@
|
|||
// (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, Input, OnInit } from '@angular/core';
|
||||
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
|
||||
import { CoreTimeUtilsProvider } from '../../../../providers/utils/time';
|
||||
import { CoreUtilsProvider } from '../../../../providers/utils/utils';
|
||||
|
||||
/**
|
||||
* Directive to render a datetime user profile field.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'addon-user-profile-field-datetime',
|
||||
templateUrl: 'datetime.html'
|
||||
})
|
||||
export class AddonUserProfileFieldDatetimeComponent implements OnInit {
|
||||
@Input() field: any; // The profile field to be rendered.
|
||||
@Input() edit? = false; // True if editing the field. Defaults to false.
|
||||
@Input() disabled? = false; // True if disabled. Defaults to false.
|
||||
@Input() form?: FormGroup; // Form where to add the form control.
|
||||
|
||||
constructor(private fb: FormBuilder, private timeUtils: CoreTimeUtilsProvider, protected utils: CoreUtilsProvider) { }
|
||||
|
||||
/**
|
||||
* Component being initialized.
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
const field = this.field;
|
||||
let year;
|
||||
|
||||
if (field && this.edit && this.form) {
|
||||
field.modelName = 'profile_field_' + field.shortname;
|
||||
|
||||
// Check if it's only date or it has time too.
|
||||
const hasTime = this.utils.isTrueOrOne(field.param3);
|
||||
field.format = hasTime ? this.timeUtils.getLocalizedDateFormat('LLL') : this.timeUtils.getLocalizedDateFormat('LL');
|
||||
|
||||
// Check min value.
|
||||
if (field.param1) {
|
||||
year = parseInt(field.param1, 10);
|
||||
if (year) {
|
||||
field.min = year;
|
||||
}
|
||||
}
|
||||
|
||||
// Check max value.
|
||||
if (field.param2) {
|
||||
year = parseInt(field.param2, 10);
|
||||
if (year) {
|
||||
field.max = year;
|
||||
}
|
||||
}
|
||||
|
||||
const formData = {
|
||||
value: field.defaultdata,
|
||||
disabled: this.disabled
|
||||
};
|
||||
this.form.addControl(field.modelName, this.fb.control(formData,
|
||||
field.required && !field.locked ? Validators.required : null));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
// (C) Copyright 2015 Martin Dougiamas
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { NgModule } from '@angular/core';
|
||||
import { IonicModule } from 'ionic-angular';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { AddonUserProfileFieldDatetimeHandler } from './providers/handler';
|
||||
import { CoreUserProfileFieldDelegate } from '../../../core/user/providers/user-profile-field-delegate';
|
||||
import { AddonUserProfileFieldDatetimeComponent } from './component/datetime';
|
||||
import { CoreComponentsModule } from '../../../components/components.module';
|
||||
import { CorePipesModule } from '../../../pipes/pipes.module';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AddonUserProfileFieldDatetimeComponent
|
||||
],
|
||||
imports: [
|
||||
IonicModule,
|
||||
TranslateModule.forChild(),
|
||||
CoreComponentsModule,
|
||||
CorePipesModule
|
||||
],
|
||||
providers: [
|
||||
AddonUserProfileFieldDatetimeHandler
|
||||
],
|
||||
exports: [
|
||||
AddonUserProfileFieldDatetimeComponent
|
||||
],
|
||||
entryComponents: [
|
||||
AddonUserProfileFieldDatetimeComponent
|
||||
]
|
||||
})
|
||||
export class AddonUserProfileFieldDatetimeModule {
|
||||
constructor(userProfileFieldDelegate: CoreUserProfileFieldDelegate, handler: AddonUserProfileFieldDatetimeHandler) {
|
||||
userProfileFieldDelegate.registerHandler(handler);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
|
||||
// (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 { CoreUserProfileFieldHandler, CoreUserProfileFieldHandlerData } from
|
||||
'../../../../core/user/providers/user-profile-field-delegate';
|
||||
import { AddonUserProfileFieldDatetimeComponent } from '../component/datetime';
|
||||
|
||||
/**
|
||||
* Datetime user profile field handlers.
|
||||
*/
|
||||
@Injectable()
|
||||
export class AddonUserProfileFieldDatetimeHandler implements CoreUserProfileFieldHandler {
|
||||
name = 'datetime';
|
||||
|
||||
constructor() {
|
||||
// Nothing to do.
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not the handler is enabled on a site level.
|
||||
*
|
||||
* @return {boolean|Promise<boolean>} True or promise resolved with true if enabled.
|
||||
*/
|
||||
isEnabled(): boolean | Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the data to send for the field based on the input data.
|
||||
*
|
||||
* @param {any} field User field to get the data for.
|
||||
* @param {boolean} signup True if user is in signup page.
|
||||
* @param {string} [registerAuth] Register auth method. E.g. 'email'.
|
||||
* @param {any} formValues Form Values.
|
||||
* @return {CoreUserProfileFieldHandlerData} Data to send for the field.
|
||||
*/
|
||||
getData(field: any, signup: boolean, registerAuth: string, formValues: any): CoreUserProfileFieldHandlerData {
|
||||
const name = 'profile_field_' + field.shortname;
|
||||
|
||||
if (formValues[name]) {
|
||||
const milliseconds = new Date(formValues[name]).getTime();
|
||||
|
||||
return {
|
||||
type: 'datetime',
|
||||
name: 'profile_field_' + field.shortname,
|
||||
value: Math.round(milliseconds / 1000)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Component to use to display the user profile field.
|
||||
*
|
||||
* @return {any} The component to use, undefined if not found.
|
||||
*/
|
||||
getComponent(): any {
|
||||
return AddonUserProfileFieldDatetimeComponent;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Render (no edit). -->
|
||||
<ion-item *ngIf="!edit && field && field.name">
|
||||
<h2>{{ field.name }}</h2>
|
||||
<p><core-format-text [text]="field.value"></core-format-text></p>
|
||||
</ion-item>
|
||||
<!-- Edit. -->
|
||||
<ion-item *ngIf="edit && field && field.shortname" text-wrap [formGroup]="form">
|
||||
<ion-label stacked [core-mark-required]="field.required">{{ field.name }}</ion-label>
|
||||
<ion-select [formControlName]="field.modelName" [placeholder]="'core.choosedots' | translate" core-input-errors>
|
||||
<ion-option value="">{{ 'core.choosedots' | translate }}</ion-option>
|
||||
<ion-option *ngFor="let option of field.options" [value]="option">{{option}}</ion-option>
|
||||
</ion-select>
|
||||
</ion-item>
|
|
@ -0,0 +1,59 @@
|
|||
// (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, Input, OnInit } from '@angular/core';
|
||||
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
|
||||
|
||||
/**
|
||||
* Directive to render a menu user profile field.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'addon-user-profile-field-menu',
|
||||
templateUrl: 'menu.html'
|
||||
})
|
||||
export class AddonUserProfileFieldMenuComponent implements OnInit {
|
||||
@Input() field: any; // The profile field to be rendered.
|
||||
@Input() edit? = false; // True if editing the field. Defaults to false.
|
||||
@Input() disabled? = false; // True if disabled. Defaults to false.
|
||||
@Input() form?: FormGroup; // Form where to add the form control.
|
||||
|
||||
constructor(private fb: FormBuilder) { }
|
||||
|
||||
/**
|
||||
* Component being initialized.
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
const field = this.field;
|
||||
|
||||
if (field && this.edit && this.form) {
|
||||
field.modelName = 'profile_field_' + field.shortname;
|
||||
|
||||
// Parse options.
|
||||
if (field.param1) {
|
||||
field.options = field.param1.split(/\r\n|\r|\n/g);
|
||||
} else {
|
||||
field.options = [];
|
||||
}
|
||||
|
||||
const formData = {
|
||||
value: field.defaultdata,
|
||||
disabled: this.disabled
|
||||
};
|
||||
// Initialize the value using default data.
|
||||
this.form.addControl(field.modelName, this.fb.control(formData,
|
||||
field.required && !field.locked ? Validators.required : null));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
// (C) Copyright 2015 Martin Dougiamas
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { NgModule } from '@angular/core';
|
||||
import { IonicModule } from 'ionic-angular';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { AddonUserProfileFieldMenuHandler } from './providers/handler';
|
||||
import { CoreUserProfileFieldDelegate } from '../../../core/user/providers/user-profile-field-delegate';
|
||||
import { AddonUserProfileFieldMenuComponent } from './component/menu';
|
||||
import { CoreComponentsModule } from '../../../components/components.module';
|
||||
import { CoreDirectivesModule } from '../../../directives/directives.module';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AddonUserProfileFieldMenuComponent
|
||||
],
|
||||
imports: [
|
||||
IonicModule,
|
||||
TranslateModule.forChild(),
|
||||
CoreComponentsModule,
|
||||
CoreDirectivesModule
|
||||
],
|
||||
providers: [
|
||||
AddonUserProfileFieldMenuHandler
|
||||
],
|
||||
exports: [
|
||||
AddonUserProfileFieldMenuComponent
|
||||
],
|
||||
entryComponents: [
|
||||
AddonUserProfileFieldMenuComponent
|
||||
]
|
||||
})
|
||||
export class AddonUserProfileFieldMenuModule {
|
||||
constructor(userProfileFieldDelegate: CoreUserProfileFieldDelegate, handler: AddonUserProfileFieldMenuHandler) {
|
||||
userProfileFieldDelegate.registerHandler(handler);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
|
||||
// (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 { CoreUserProfileFieldHandler, CoreUserProfileFieldHandlerData } from
|
||||
'../../../../core/user/providers/user-profile-field-delegate';
|
||||
import { AddonUserProfileFieldMenuComponent } from '../component/menu';
|
||||
|
||||
/**
|
||||
* Menu user profile field handlers.
|
||||
*/
|
||||
@Injectable()
|
||||
export class AddonUserProfileFieldMenuHandler implements CoreUserProfileFieldHandler {
|
||||
name = 'menu';
|
||||
|
||||
constructor() {
|
||||
// Nothing to do.
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not the handler is enabled on a site level.
|
||||
*
|
||||
* @return {boolean|Promise<boolean>} True or promise resolved with true if enabled.
|
||||
*/
|
||||
isEnabled(): boolean | Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the data to send for the field based on the input data.
|
||||
*
|
||||
* @param {any} field User field to get the data for.
|
||||
* @param {boolean} signup True if user is in signup page.
|
||||
* @param {string} [registerAuth] Register auth method. E.g. 'email'.
|
||||
* @param {any} formValues Form Values.
|
||||
* @return {CoreUserProfileFieldHandlerData} Data to send for the field.
|
||||
*/
|
||||
getData(field: any, signup: boolean, registerAuth: string, formValues: any): CoreUserProfileFieldHandlerData {
|
||||
const name = 'profile_field_' + field.shortname;
|
||||
|
||||
if (formValues[name]) {
|
||||
return {
|
||||
type: 'menu',
|
||||
name: name,
|
||||
value: formValues[name]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Component to use to display the user profile field.
|
||||
*
|
||||
* @return {any} The component to use, undefined if not found.
|
||||
*/
|
||||
getComponent(): any {
|
||||
return AddonUserProfileFieldMenuComponent;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
<!-- Render (no edit). -->
|
||||
<ion-item *ngIf="!edit && field && field.name">
|
||||
<h2>{{ field.name }}</h2>
|
||||
<p><core-format-text [text]="field.value"></core-format-text></p>
|
||||
</ion-item>
|
||||
<!-- Edit. -->
|
||||
<ion-item *ngIf="edit && field && field.shortname" text-wrap [formGroup]="form">
|
||||
<ion-label stacked [core-mark-required]="field.required">{{ field.name }}</ion-label>
|
||||
<ion-input [type]="field.inputType" [formControlName]="field.modelName" [placeholder]="field.name" maxlength="{{field.maxlength}}" core-input-errors></ion-input>
|
||||
</ion-item>
|
|
@ -0,0 +1,60 @@
|
|||
// (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, Input, OnInit } from '@angular/core';
|
||||
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
|
||||
import { CoreUtilsProvider } from '../../../../providers/utils/utils';
|
||||
|
||||
/**
|
||||
* Directive to render a text user profile field.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'addon-user-profile-field-text',
|
||||
templateUrl: 'text.html'
|
||||
})
|
||||
export class AddonUserProfileFieldTextComponent implements OnInit {
|
||||
@Input() field: any; // The profile field to be rendered.
|
||||
@Input() edit? = false; // True if editing the field. Defaults to false.
|
||||
@Input() disabled? = false; // True if disabled. Defaults to false.
|
||||
@Input() form?: FormGroup; // Form where to add the form control.
|
||||
|
||||
constructor(private fb: FormBuilder, protected utils: CoreUtilsProvider) { }
|
||||
|
||||
/**
|
||||
* Component being initialized.
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
const field = this.field;
|
||||
|
||||
if (field && this.edit && this.form) {
|
||||
field.modelName = 'profile_field_' + field.shortname;
|
||||
|
||||
// Check max length.
|
||||
if (field.param2) {
|
||||
field.maxlength = parseInt(field.param2, 10) || '';
|
||||
}
|
||||
|
||||
// Check if it's a password or text.
|
||||
field.inputType = this.utils.isTrueOrOne(field.param3) ? 'password' : 'text';
|
||||
|
||||
const formData = {
|
||||
value: field.defaultdata,
|
||||
disabled: this.disabled
|
||||
};
|
||||
// Initialize the value using default data.
|
||||
this.form.addControl(field.modelName, this.fb.control(formData,
|
||||
field.required && !field.locked ? Validators.required : null));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
|
||||
// (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 { CoreUserProfileFieldHandler, CoreUserProfileFieldHandlerData } from
|
||||
'../../../../core/user/providers/user-profile-field-delegate';
|
||||
import { AddonUserProfileFieldTextComponent } from '../component/text';
|
||||
import { CoreTextUtilsProvider } from '../../../../providers/utils/text';
|
||||
|
||||
/**
|
||||
* Text user profile field handlers.
|
||||
*/
|
||||
@Injectable()
|
||||
export class AddonUserProfileFieldTextHandler implements CoreUserProfileFieldHandler {
|
||||
name = 'text';
|
||||
|
||||
constructor(private textUtils: CoreTextUtilsProvider) { }
|
||||
|
||||
/**
|
||||
* Whether or not the handler is enabled on a site level.
|
||||
*
|
||||
* @return {boolean|Promise<boolean>} True or promise resolved with true if enabled.
|
||||
*/
|
||||
isEnabled(): boolean | Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the data to send for the field based on the input data.
|
||||
*
|
||||
* @param {any} field User field to get the data for.
|
||||
* @param {boolean} signup True if user is in signup page.
|
||||
* @param {string} [registerAuth] Register auth method. E.g. 'email'.
|
||||
* @param {any} formValues Form Values.
|
||||
* @return {CoreUserProfileFieldHandlerData} Data to send for the field.
|
||||
*/
|
||||
getData(field: any, signup: boolean, registerAuth: string, formValues: any): CoreUserProfileFieldHandlerData {
|
||||
const name = 'profile_field_' + field.shortname;
|
||||
|
||||
return {
|
||||
type: 'text',
|
||||
name: name,
|
||||
value: this.textUtils.cleanTags(formValues[name])
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Component to use to display the user profile field.
|
||||
*
|
||||
* @return {any} The component to use, undefined if not found.
|
||||
*/
|
||||
getComponent(): any {
|
||||
return AddonUserProfileFieldTextComponent;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
// (C) Copyright 2015 Martin Dougiamas
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { NgModule } from '@angular/core';
|
||||
import { IonicModule } from 'ionic-angular';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { AddonUserProfileFieldTextHandler } from './providers/handler';
|
||||
import { CoreUserProfileFieldDelegate } from '../../../core/user/providers/user-profile-field-delegate';
|
||||
import { AddonUserProfileFieldTextComponent } from './component/text';
|
||||
import { CoreComponentsModule } from '../../../components/components.module';
|
||||
import { CoreDirectivesModule } from '../../../directives/directives.module';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AddonUserProfileFieldTextComponent
|
||||
],
|
||||
imports: [
|
||||
IonicModule,
|
||||
TranslateModule.forChild(),
|
||||
CoreComponentsModule,
|
||||
CoreDirectivesModule
|
||||
],
|
||||
providers: [
|
||||
AddonUserProfileFieldTextHandler
|
||||
],
|
||||
exports: [
|
||||
AddonUserProfileFieldTextComponent
|
||||
],
|
||||
entryComponents: [
|
||||
AddonUserProfileFieldTextComponent
|
||||
]
|
||||
})
|
||||
export class AddonUserProfileFieldTextModule {
|
||||
constructor(userProfileFieldDelegate: CoreUserProfileFieldDelegate, handler: AddonUserProfileFieldTextHandler) {
|
||||
userProfileFieldDelegate.registerHandler(handler);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
<!-- Render (no edit). -->
|
||||
<ion-item *ngIf="!edit && field && field.name">
|
||||
<h2>{{ field.name }}</h2>
|
||||
<p><core-format-text [text]="field.value"></core-format-text></p>
|
||||
</ion-item>
|
||||
<!-- Edit. -->
|
||||
<ion-item *ngIf="edit && field && field.shortname" text-wrap [formGroup]="form">
|
||||
<ion-label stacked [core-mark-required]="field.required">{{ field.name }}</ion-label>
|
||||
<core-rich-text-editor item-content [control]="control" [placeholder]="field.name"></core-rich-text-editor>
|
||||
</ion-item>
|
|
@ -0,0 +1,55 @@
|
|||
// (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, Input, OnInit } from '@angular/core';
|
||||
import { FormGroup, Validators, FormControl } from '@angular/forms';
|
||||
|
||||
/**
|
||||
* Directive to render a textarea user profile field.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'addon-user-profile-field-textarea',
|
||||
templateUrl: 'textarea.html'
|
||||
})
|
||||
export class AddonUserProfileFieldTextareaComponent implements OnInit {
|
||||
@Input() field: any; // The profile field to be rendered.
|
||||
@Input() edit? = false; // True if editing the field. Defaults to false.
|
||||
@Input() disabled? = false; // True if disabled. Defaults to false.
|
||||
@Input() form?: FormGroup; // Form where to add the form control.
|
||||
|
||||
control: FormControl;
|
||||
|
||||
constructor() {
|
||||
// Nothing to do.
|
||||
}
|
||||
|
||||
/**
|
||||
* Component being initialized.
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
const field = this.field;
|
||||
|
||||
if (field && this.edit && this.form) {
|
||||
field.modelName = 'profile_field_' + field.shortname;
|
||||
|
||||
const formData = {
|
||||
value: field.defaultdata,
|
||||
disabled: this.disabled
|
||||
};
|
||||
|
||||
this.control = new FormControl(formData, field.required && !field.locked ? Validators.required : null);
|
||||
this.form.addControl(field.modelName, this.control);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
|
||||
// (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 { CoreUserProfileFieldHandler, CoreUserProfileFieldHandlerData } from
|
||||
'../../../../core/user/providers/user-profile-field-delegate';
|
||||
import { AddonUserProfileFieldTextareaComponent } from '../component/textarea';
|
||||
import { CoreTextUtilsProvider } from '../../../../providers/utils/text';
|
||||
|
||||
/**
|
||||
* Textarea user profile field handlers.
|
||||
*/
|
||||
@Injectable()
|
||||
export class AddonUserProfileFieldTextareaHandler implements CoreUserProfileFieldHandler {
|
||||
name = 'textarea';
|
||||
|
||||
constructor(private textUtils: CoreTextUtilsProvider) { }
|
||||
|
||||
/**
|
||||
* Whether or not the handler is enabled on a site level.
|
||||
*
|
||||
* @return {boolean|Promise<boolean>} True or promise resolved with true if enabled.
|
||||
*/
|
||||
isEnabled(): boolean | Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the data to send for the field based on the input data.
|
||||
*
|
||||
* @param {any} field User field to get the data for.
|
||||
* @param {boolean} signup True if user is in signup page.
|
||||
* @param {string} [registerAuth] Register auth method. E.g. 'email'.
|
||||
* @param {any} formValues Form Values.
|
||||
* @return {CoreUserProfileFieldHandlerData} Data to send for the field.
|
||||
*/
|
||||
getData(field: any, signup: boolean, registerAuth: string, formValues: any): CoreUserProfileFieldHandlerData {
|
||||
const name = 'profile_field_' + field.shortname;
|
||||
|
||||
if (formValues[name]) {
|
||||
let text = formValues[name] || '';
|
||||
// Add some HTML to the message in case the user edited with textarea.
|
||||
text = this.textUtils.formatHtmlLines(text);
|
||||
|
||||
return {
|
||||
type: 'textarea',
|
||||
name: name,
|
||||
value: JSON.stringify({
|
||||
text: text,
|
||||
format: 1 // Always send this format.
|
||||
})
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Component to use to display the user profile field.
|
||||
*
|
||||
* @return {any} The component to use, undefined if not found.
|
||||
*/
|
||||
getComponent(): any {
|
||||
return AddonUserProfileFieldTextareaComponent;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
// (C) Copyright 2015 Martin Dougiamas
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { NgModule } from '@angular/core';
|
||||
import { IonicModule } from 'ionic-angular';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { AddonUserProfileFieldTextareaHandler } from './providers/handler';
|
||||
import { CoreUserProfileFieldDelegate } from '../../../core/user/providers/user-profile-field-delegate';
|
||||
import { AddonUserProfileFieldTextareaComponent } from './component/textarea';
|
||||
import { CoreComponentsModule } from '../../../components/components.module';
|
||||
import { CoreDirectivesModule } from '../../../directives/directives.module';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AddonUserProfileFieldTextareaComponent
|
||||
],
|
||||
imports: [
|
||||
IonicModule,
|
||||
TranslateModule.forChild(),
|
||||
CoreComponentsModule,
|
||||
CoreDirectivesModule
|
||||
],
|
||||
providers: [
|
||||
AddonUserProfileFieldTextareaHandler
|
||||
],
|
||||
exports: [
|
||||
AddonUserProfileFieldTextareaComponent
|
||||
],
|
||||
entryComponents: [
|
||||
AddonUserProfileFieldTextareaComponent
|
||||
]
|
||||
})
|
||||
export class AddonUserProfileFieldTextareaModule {
|
||||
constructor(userProfileFieldDelegate: CoreUserProfileFieldDelegate, handler: AddonUserProfileFieldTextareaHandler) {
|
||||
userProfileFieldDelegate.registerHandler(handler);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
// (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 { AddonUserProfileFieldCheckboxModule } from './checkbox/checkbox.module';
|
||||
import { AddonUserProfileFieldDatetimeModule } from './datetime/datetime.module';
|
||||
import { AddonUserProfileFieldMenuModule } from './menu/menu.module';
|
||||
import { AddonUserProfileFieldTextModule } from './text/text.module';
|
||||
import { AddonUserProfileFieldTextareaModule } from './textarea/textarea.module';
|
||||
|
||||
@NgModule({
|
||||
declarations: [],
|
||||
imports: [
|
||||
AddonUserProfileFieldCheckboxModule,
|
||||
AddonUserProfileFieldDatetimeModule,
|
||||
AddonUserProfileFieldMenuModule,
|
||||
AddonUserProfileFieldTextModule,
|
||||
AddonUserProfileFieldTextareaModule
|
||||
],
|
||||
providers: [
|
||||
],
|
||||
exports: []
|
||||
})
|
||||
export class AddonUserProfileFieldModule { }
|
|
@ -25,15 +25,15 @@ import { CoreLoginHelperProvider } from '../core/login/providers/helper';
|
|||
templateUrl: 'app.html'
|
||||
})
|
||||
export class MoodleMobileApp implements OnInit {
|
||||
// Use the page name (string) because the page is lazy loaded (Ionic feature). That way we can load pages without
|
||||
// having to import them. The downside is that each page needs to implement a ngModule.
|
||||
rootPage:any = 'CoreLoginInitPage';
|
||||
// Use page name (string) because the page is lazy loaded (Ionic feature). That way we can load pages without importing them.
|
||||
// The downside is that each page needs to implement a ngModule.
|
||||
rootPage: any = 'CoreLoginInitPage';
|
||||
protected logger;
|
||||
protected lastUrls = {};
|
||||
|
||||
constructor(private platform: Platform, statusBar: StatusBar, splashScreen: SplashScreen, logger: CoreLoggerProvider,
|
||||
private eventsProvider: CoreEventsProvider, private loginHelper: CoreLoginHelperProvider,
|
||||
private appProvider: CoreAppProvider) {
|
||||
private eventsProvider: CoreEventsProvider, private loginHelper: CoreLoginHelperProvider,
|
||||
private appProvider: CoreAppProvider) {
|
||||
this.logger = logger.getInstance('AppComponent');
|
||||
|
||||
platform.ready().then(() => {
|
||||
|
@ -48,7 +48,7 @@ export class MoodleMobileApp implements OnInit {
|
|||
/**
|
||||
* Component being initialized.
|
||||
*/
|
||||
ngOnInit() {
|
||||
ngOnInit(): void {
|
||||
// Go to sites page when user is logged out.
|
||||
this.eventsProvider.on(CoreEventsProvider.LOGOUT, () => {
|
||||
this.appProvider.getRootNavController().setRoot('CoreLoginSitesPage');
|
||||
|
@ -93,7 +93,7 @@ export class MoodleMobileApp implements OnInit {
|
|||
});
|
||||
|
||||
// Handle app launched with a certain URL (custom URL scheme).
|
||||
(<any>window).handleOpenURL = (url: string) => {
|
||||
(<any> window).handleOpenURL = (url: string): void => {
|
||||
// First check that the URL hasn't been treated a few seconds ago. Sometimes this function is called more than once.
|
||||
if (this.lastUrls[url] && Date.now() - this.lastUrls[url] < 3000) {
|
||||
// Function called more than once, stop.
|
||||
|
@ -113,4 +113,3 @@ export class MoodleMobileApp implements OnInit {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -60,12 +60,14 @@ import { CoreSharedFilesModule } from '../core/sharedfiles/sharedfiles.module';
|
|||
import { CoreCourseModule } from '../core/course/course.module';
|
||||
import { CoreSiteHomeModule } from '../core/sitehome/sitehome.module';
|
||||
import { CoreContentLinksModule } from '../core/contentlinks/contentlinks.module';
|
||||
import { CoreUserModule } from '../core/user/user.module';
|
||||
|
||||
// Addon modules.
|
||||
import { AddonCalendarModule } from '../addon/calendar/calendar.module';
|
||||
import { AddonUserProfileFieldModule } from '../addon/userprofilefield/userprofilefield.module';
|
||||
|
||||
// For translate loader. AoT requires an exported function for factories.
|
||||
export function createTranslateLoader(http: HttpClient) {
|
||||
export function createTranslateLoader(http: HttpClient): TranslateHttpLoader {
|
||||
return new TranslateHttpLoader(http, './assets/lang/', '.json');
|
||||
}
|
||||
|
||||
|
@ -97,7 +99,9 @@ export function createTranslateLoader(http: HttpClient) {
|
|||
CoreCourseModule,
|
||||
CoreSiteHomeModule,
|
||||
CoreContentLinksModule,
|
||||
AddonCalendarModule
|
||||
CoreUserModule,
|
||||
AddonCalendarModule,
|
||||
AddonUserProfileFieldModule
|
||||
],
|
||||
bootstrap: [IonicApp],
|
||||
entryComponents: [
|
||||
|
|
|
@ -115,7 +115,8 @@
|
|||
}
|
||||
|
||||
> img:first-child,
|
||||
ion-avatar img {
|
||||
ion-avatar img,
|
||||
img {
|
||||
display: block;
|
||||
margin: auto;
|
||||
width: 90px;
|
||||
|
|
|
@ -1,3 +1,17 @@
|
|||
// (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 { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||
|
||||
import { AppModule } from './app.module';
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
|
@ -32,9 +32,9 @@ export class CoreSyncBaseProvider {
|
|||
syncInterval = 300000;
|
||||
|
||||
// Store sync promises.
|
||||
protected syncPromises: {[siteId: string]: {[uniqueId: string]: Promise<any>}} = {};
|
||||
protected syncPromises: { [siteId: string]: { [uniqueId: string]: Promise<any> } } = {};
|
||||
|
||||
constructor(private sitesProvider: CoreSitesProvider) {}
|
||||
constructor(private sitesProvider: CoreSitesProvider) { }
|
||||
|
||||
/**
|
||||
* Add an ongoing sync to the syncPromises list. On finish the promise will be removed.
|
||||
|
@ -44,7 +44,7 @@ export class CoreSyncBaseProvider {
|
|||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} The sync promise.
|
||||
*/
|
||||
addOngoingSync(id: number, promise: Promise<any>, siteId?: string) : Promise<any> {
|
||||
addOngoingSync(id: number, promise: Promise<any>, siteId?: string): Promise<any> {
|
||||
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||
|
||||
const uniqueId = this.getUniqueSyncId(id);
|
||||
|
@ -67,12 +67,13 @@ export class CoreSyncBaseProvider {
|
|||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Promise of the current sync or undefined if there isn't any.
|
||||
*/
|
||||
getOngoingSync(id: number, siteId?: string) : Promise<any> {
|
||||
getOngoingSync(id: number, siteId?: string): Promise<any> {
|
||||
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||
|
||||
if (this.isSyncing(id, siteId)) {
|
||||
// There's already a sync ongoing for this discussion, return the promise.
|
||||
const uniqueId = this.getUniqueSyncId(id);
|
||||
|
||||
return this.syncPromises[siteId][uniqueId];
|
||||
}
|
||||
}
|
||||
|
@ -84,9 +85,9 @@ export class CoreSyncBaseProvider {
|
|||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<number>} Promise resolved with the time.
|
||||
*/
|
||||
getSyncTime(id: number, siteId?: string) : Promise<number> {
|
||||
getSyncTime(id: number, siteId?: string): Promise<number> {
|
||||
return this.sitesProvider.getSiteDb(siteId).then((db) => {
|
||||
return db.getRecord(CoreSyncProvider.SYNC_TABLE, {component: this.component, id: id}).then((entry) => {
|
||||
return db.getRecord(CoreSyncProvider.SYNC_TABLE, { component: this.component, id: id }).then((entry) => {
|
||||
return entry.time;
|
||||
}).catch(() => {
|
||||
return 0;
|
||||
|
@ -101,12 +102,12 @@ export class CoreSyncBaseProvider {
|
|||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<string[]>} Promise resolved with the warnings.
|
||||
*/
|
||||
getSyncWarnings(id: number, siteId?: string) : Promise<string[]> {
|
||||
getSyncWarnings(id: number, siteId?: string): Promise<string[]> {
|
||||
return this.sitesProvider.getSiteDb(siteId).then((db) => {
|
||||
return db.getRecord(CoreSyncProvider.SYNC_TABLE, {component: this.component, id: id}).then((entry) => {
|
||||
return db.getRecord(CoreSyncProvider.SYNC_TABLE, { component: this.component, id: id }).then((entry) => {
|
||||
try {
|
||||
return JSON.parse(entry.warnings);
|
||||
} catch(ex) {
|
||||
} catch (ex) {
|
||||
return [];
|
||||
}
|
||||
}).catch(() => {
|
||||
|
@ -121,7 +122,7 @@ export class CoreSyncBaseProvider {
|
|||
* @param {number} id Unique sync identifier per component.
|
||||
* @return {string} Unique identifier from component and id.
|
||||
*/
|
||||
protected getUniqueSyncId(id: number) : string {
|
||||
protected getUniqueSyncId(id: number): string {
|
||||
return this.component + '#' + id;
|
||||
}
|
||||
|
||||
|
@ -132,10 +133,11 @@ export class CoreSyncBaseProvider {
|
|||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {boolean} Whether it's synchronizing.
|
||||
*/
|
||||
isSyncing(id: number, siteId?: string) : boolean {
|
||||
isSyncing(id: number, siteId?: string): boolean {
|
||||
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||
|
||||
const uniqueId = this.getUniqueSyncId(id);
|
||||
|
||||
return !!(this.syncPromises[siteId] && this.syncPromises[siteId][uniqueId]);
|
||||
}
|
||||
|
||||
|
@ -146,7 +148,7 @@ export class CoreSyncBaseProvider {
|
|||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<boolean>} Promise resolved with boolean: whether sync is needed.
|
||||
*/
|
||||
isSyncNeeded(id: number, siteId?: string) : Promise<boolean> {
|
||||
isSyncNeeded(id: number, siteId?: string): Promise<boolean> {
|
||||
return this.getSyncTime(id, siteId).then((time) => {
|
||||
return Date.now() - this.syncInterval >= time;
|
||||
});
|
||||
|
@ -160,10 +162,11 @@ export class CoreSyncBaseProvider {
|
|||
* @param {number} [time] Time to set. If not defined, current time.
|
||||
* @return {Promise<any>} Promise resolved when the time is set.
|
||||
*/
|
||||
setSyncTime(id: number, siteId?: string, time?: number) : Promise<any> {
|
||||
setSyncTime(id: number, siteId?: string, time?: number): Promise<any> {
|
||||
return this.sitesProvider.getSiteDb(siteId).then((db) => {
|
||||
time = typeof time != 'undefined' ? time : Date.now();
|
||||
return db.insertOrUpdateRecord(CoreSyncProvider.SYNC_TABLE, {time: time}, {component: this.component, id: id});
|
||||
|
||||
return db.insertOrUpdateRecord(CoreSyncProvider.SYNC_TABLE, { time: time }, { component: this.component, id: id });
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -175,11 +178,12 @@ export class CoreSyncBaseProvider {
|
|||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Promise resolved when done.
|
||||
*/
|
||||
setSyncWarnings(id: number, warnings: string[], siteId?: string) : Promise<any> {
|
||||
setSyncWarnings(id: number, warnings: string[], siteId?: string): Promise<any> {
|
||||
return this.sitesProvider.getSiteDb(siteId).then((db) => {
|
||||
warnings = warnings || [];
|
||||
return db.insertOrUpdateRecord(CoreSyncProvider.SYNC_TABLE, {warnings: JSON.stringify(warnings)},
|
||||
{component: this.component, id: id});
|
||||
|
||||
return db.insertOrUpdateRecord(CoreSyncProvider.SYNC_TABLE, { warnings: JSON.stringify(warnings) },
|
||||
{ component: this.component, id: id });
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -191,11 +195,14 @@ export class CoreSyncBaseProvider {
|
|||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Promise resolved when there's no sync going on for the identifier.
|
||||
*/
|
||||
waitForSync(id: number, siteId?: string) : Promise<any> {
|
||||
waitForSync(id: number, siteId?: string): Promise<any> {
|
||||
const promise = this.getOngoingSync(id, siteId);
|
||||
if (promise) {
|
||||
return promise.catch(() => {});
|
||||
return promise.catch(() => {
|
||||
// Ignore errors.
|
||||
});
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,12 +23,14 @@
|
|||
export class CoreCache {
|
||||
protected cacheStore = {};
|
||||
|
||||
constructor() {}
|
||||
constructor() {
|
||||
// Nothing to do.
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the cache.
|
||||
*/
|
||||
clear() {
|
||||
clear(): void {
|
||||
this.cacheStore = {};
|
||||
}
|
||||
|
||||
|
@ -38,7 +40,7 @@ export class CoreCache {
|
|||
* @param {any} id The ID to identify the entry.
|
||||
* @return {any} The data from the cache. Undefined if not found.
|
||||
*/
|
||||
getEntry(id: any) : any {
|
||||
getEntry(id: any): any {
|
||||
if (!this.cacheStore[id]) {
|
||||
this.cacheStore[id] = {};
|
||||
}
|
||||
|
@ -54,7 +56,7 @@ export class CoreCache {
|
|||
* @param {boolean} [ignoreInvalidate] Whether it should always return the cached data, even if it's expired.
|
||||
* @return {any} Cached value. Undefined if not cached or expired.
|
||||
*/
|
||||
getValue(id: any, name: string, ignoreInvalidate?: boolean) : any {
|
||||
getValue(id: any, name: string, ignoreInvalidate?: boolean): any {
|
||||
const entry = this.getEntry(id);
|
||||
|
||||
if (entry[name] && typeof entry[name].value != 'undefined') {
|
||||
|
@ -73,9 +75,9 @@ export class CoreCache {
|
|||
*
|
||||
* @param {any} id The ID to identify the entry.
|
||||
*/
|
||||
invalidate(id: any) : void {
|
||||
invalidate(id: any): void {
|
||||
const entry = this.getEntry(id);
|
||||
for (let name in entry) {
|
||||
for (const name in entry) {
|
||||
entry[name].timemodified = 0;
|
||||
}
|
||||
}
|
||||
|
@ -88,12 +90,13 @@ export class CoreCache {
|
|||
* @param {any} value Value to set.
|
||||
* @return {any} The set value.
|
||||
*/
|
||||
setValue(id: any, name: string, value: any) : any {
|
||||
setValue(id: any, name: string, value: any): any {
|
||||
const entry = this.getEntry(id);
|
||||
entry[name] = {
|
||||
value: value,
|
||||
timemodified: Date.now()
|
||||
};
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,283 @@
|
|||
// (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 { CoreEventsProvider } from '../providers/events';
|
||||
|
||||
export interface CoreDelegateHandler {
|
||||
/**
|
||||
* Name of the handler, or name and sub context (AddonMessages, AddonMessages:blockContact, ...).
|
||||
* @type {string}
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* Whether or not the handler is enabled on a site level.
|
||||
* @return {boolean|Promise<boolean>} Whether or not the handler is enabled on a site level.
|
||||
*/
|
||||
isEnabled(): boolean | Promise<boolean>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Superclass to help creating delegates
|
||||
*/
|
||||
@Injectable()
|
||||
export class CoreDelegate {
|
||||
|
||||
/**
|
||||
* Logger instance get from CoreLoggerProvider.
|
||||
* @type {any}
|
||||
*/
|
||||
protected logger;
|
||||
|
||||
/**
|
||||
* List of registered handlers.
|
||||
* @type {any}
|
||||
*/
|
||||
protected handlers: { [s: string]: CoreDelegateHandler } = {};
|
||||
|
||||
/**
|
||||
* List of registered handlers enabled for the current site.
|
||||
* @type {any}
|
||||
*/
|
||||
protected enabledHandlers: { [s: string]: CoreDelegateHandler } = {};
|
||||
|
||||
/**
|
||||
* Default handler
|
||||
* @type {CoreDelegateHandler}
|
||||
*/
|
||||
protected defaultHandler: CoreDelegateHandler;
|
||||
|
||||
/**
|
||||
* Time when last updateHandler functions started.
|
||||
* @type {number}
|
||||
*/
|
||||
protected lastUpdateHandlersStart: number;
|
||||
|
||||
/**
|
||||
* Feature prefix to check is feature is enabled or disabled in site.
|
||||
* This check is only made if not false. Override on the subclass or override isFeatureDisabled function.
|
||||
* @type {string}
|
||||
*/
|
||||
protected featurePrefix: string;
|
||||
|
||||
/**
|
||||
* Constructor of the Delegate.
|
||||
*
|
||||
* @param {string} delegateName Delegate name used for logging purposes.
|
||||
* @param {CoreLoggerProvider} loggerProvider CoreLoggerProvider instance, cannot be directly injected.
|
||||
* @param {CoreSitesProvider} sitesProvider CoreSitesProvider instance, cannot be directly injected.
|
||||
* @param {CoreEventsProvider} [eventsProvider] CoreEventsProvider instance, cannot be directly injected.
|
||||
* If not set, no events will be fired.
|
||||
*/
|
||||
constructor(delegateName: string, protected loggerProvider: CoreLoggerProvider, protected sitesProvider: CoreSitesProvider,
|
||||
protected eventsProvider?: CoreEventsProvider) {
|
||||
this.logger = this.loggerProvider.getInstance(delegateName);
|
||||
|
||||
if (eventsProvider) {
|
||||
// Update handlers on this cases.
|
||||
eventsProvider.on(CoreEventsProvider.LOGIN, this.updateHandlers.bind(this));
|
||||
eventsProvider.on(CoreEventsProvider.SITE_UPDATED, this.updateHandlers.bind(this));
|
||||
eventsProvider.on(CoreEventsProvider.REMOTE_ADDONS_LOADED, this.updateHandlers.bind(this));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a certain function in a enabled handler.
|
||||
* If the handler isn't found or function isn't defined, call the same function in the default handler.
|
||||
*
|
||||
* @param {string} handlerName The handler name.
|
||||
* @param {string} fnName Name of the function to execute.
|
||||
* @param {any[]} params Parameters to pass to the function.
|
||||
* @return {any} Function returned value or default value.
|
||||
*/
|
||||
protected executeFunctionOnEnabled(handlerName: string, fnName: string, params?: any[]): any {
|
||||
return this.execute(this.enabledHandlers[handlerName], fnName, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a certain function in a handler.
|
||||
* If the handler isn't found or function isn't defined, call the same function in the default handler.
|
||||
*
|
||||
* @param {string} handlerName The handler name.
|
||||
* @param {string} fnName Name of the function to execute.
|
||||
* @param {any[]} params Parameters to pass to the function.
|
||||
* @return {any} Function returned value or default value.
|
||||
*/
|
||||
protected executeFunction(handlerName: string, fnName: string, params?: any[]): any {
|
||||
return this.execute(this.handlers[handlerName], fnName, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a certain function in a handler.
|
||||
* If the handler isn't found or function isn't defined, call the same function in the default handler.
|
||||
*
|
||||
* @param {any} handler The handler.
|
||||
* @param {string} fnName Name of the function to execute.
|
||||
* @param {any[]} params Parameters to pass to the function.
|
||||
* @return {any} Function returned value or default value.
|
||||
*/
|
||||
private execute(handler: any, fnName: string, params?: any[]): any {
|
||||
if (handler && handler[fnName]) {
|
||||
return handler[fnName].apply(handler, params);
|
||||
} else if (this.defaultHandler && this.defaultHandler[fnName]) {
|
||||
return this.defaultHandler[fnName].apply(this, params);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a handler.
|
||||
*
|
||||
* @param {string} handlerName The handler name.
|
||||
* @param {boolean} [enabled] Only enabled, or any.
|
||||
* @return {any} Handler.
|
||||
*/
|
||||
protected getHandler(handlerName: string, enabled: boolean = false): any {
|
||||
return enabled ? this.enabledHandlers[handlerName] : this.handlers[handlerName];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a handler name has a registered handler (not necessarily enabled).
|
||||
*
|
||||
* @param {string} name The handler name.
|
||||
* @param {boolean} [enabled] Only enabled, or any.
|
||||
* @return {boolean} If the handler is registered or not.
|
||||
*/
|
||||
hasHandler(name: string, enabled: boolean = false): boolean {
|
||||
return enabled ? typeof this.enabledHandlers[name] !== 'undefined' : typeof this.handlers[name] !== 'undefined';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a time belongs to the last update handlers call.
|
||||
* This is to handle the cases where updateHandlers don't finish in the same order as they're called.
|
||||
*
|
||||
* @param {number} time Time to check.
|
||||
* @return {boolean} Whether it's the last call.
|
||||
*/
|
||||
isLastUpdateCall(time: number): boolean {
|
||||
if (!this.lastUpdateHandlersStart) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return time == this.lastUpdateHandlersStart;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a handler.
|
||||
*
|
||||
* @param {CoreDelegateHandler} handler The handler delegate object to register.
|
||||
* @return {boolean} True when registered, false if already registered.
|
||||
*/
|
||||
registerHandler(handler: CoreDelegateHandler): boolean {
|
||||
if (typeof this.handlers[handler.name] !== 'undefined') {
|
||||
this.logger.log(`Addon '${handler.name}' already registered`);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
this.logger.log(`Registered addon '${handler.name}'`);
|
||||
this.handlers[handler.name] = handler;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the handler for the current site.
|
||||
*
|
||||
* @param {CoreDelegateHandler} handler The handler to check.
|
||||
* @param {number} time Time this update process started.
|
||||
* @return {Promise<void>} Resolved when done.
|
||||
*/
|
||||
protected updateHandler(handler: CoreDelegateHandler, time: number): Promise<void> {
|
||||
const siteId = this.sitesProvider.getCurrentSiteId(),
|
||||
currentSite = this.sitesProvider.getCurrentSite();
|
||||
let promise;
|
||||
|
||||
if (!this.sitesProvider.isLoggedIn()) {
|
||||
promise = Promise.reject(null);
|
||||
} else if (this.isFeatureDisabled(handler, currentSite)) {
|
||||
promise = Promise.resolve(false);
|
||||
} else {
|
||||
promise = Promise.resolve(handler.isEnabled());
|
||||
}
|
||||
|
||||
// Checks if the handler is enabled.
|
||||
return promise.catch(() => {
|
||||
return false;
|
||||
}).then((enabled: boolean) => {
|
||||
// Verify that this call is the last one that was started.
|
||||
// Check that site hasn't changed since the check started.
|
||||
if (this.isLastUpdateCall(time) && this.sitesProvider.getCurrentSiteId() === siteId) {
|
||||
if (enabled) {
|
||||
this.enabledHandlers[handler.name] = handler;
|
||||
} else {
|
||||
delete this.enabledHandlers[handler.name];
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if feature is enabled or disabled in the site, depending on the feature prefix and the handler name.
|
||||
*
|
||||
* @param {CoreDelegateHandler} handler Handler to check.
|
||||
* @param {any} site Site to check.
|
||||
* @return {boolean} Whether is enabled or disabled in site.
|
||||
*/
|
||||
protected isFeatureDisabled(handler: CoreDelegateHandler, site: any): boolean {
|
||||
return typeof this.featurePrefix != 'undefined' && site.isFeatureDisabled(this.featurePrefix + handler.name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the handlers for the current site.
|
||||
*
|
||||
* @return {Promise<void>} Resolved when done.
|
||||
*/
|
||||
protected updateHandlers(): Promise<void> {
|
||||
const promises = [],
|
||||
now = Date.now();
|
||||
|
||||
this.logger.debug('Updating handlers for current site.');
|
||||
|
||||
this.lastUpdateHandlersStart = now;
|
||||
|
||||
// Loop over all the handlers.
|
||||
for (const name in this.handlers) {
|
||||
promises.push(this.updateHandler(this.handlers[name], now));
|
||||
}
|
||||
|
||||
return Promise.all(promises).then(() => {
|
||||
return true;
|
||||
}, () => {
|
||||
// Never reject.
|
||||
return true;
|
||||
}).then(() => {
|
||||
|
||||
// Verify that this call is the last one that was started.
|
||||
if (this.isLastUpdateCall(now)) {
|
||||
this.updateData();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update handlers Data.
|
||||
* Override this function to update handlers data.
|
||||
*/
|
||||
updateData(): any {
|
||||
// To be overridden.
|
||||
}
|
||||
}
|
|
@ -23,14 +23,16 @@ import { Observable } from 'rxjs';
|
|||
@Injectable()
|
||||
export class CoreInterceptor implements HttpInterceptor {
|
||||
|
||||
constructor() {}
|
||||
constructor() {
|
||||
// Nothing to do.
|
||||
}
|
||||
|
||||
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<any> {
|
||||
// Add the header and serialize the body if needed.
|
||||
const newReq = req.clone({
|
||||
headers: req.headers.set('Content-Type', 'application/x-www-form-urlencoded'),
|
||||
body: typeof req.body == 'object' && String(req.body) != '[object File]' ?
|
||||
CoreInterceptor.serialize(req.body) : req.body
|
||||
CoreInterceptor.serialize(req.body) : req.body
|
||||
});
|
||||
|
||||
// Pass on the cloned request instead of the original request.
|
||||
|
@ -44,14 +46,14 @@ export class CoreInterceptor implements HttpInterceptor {
|
|||
* @param {boolean} [addNull] Add null values to the serialized as empty parameters.
|
||||
* @return {string} Serialization of the object.
|
||||
*/
|
||||
public static serialize(obj: any, addNull?: boolean) : string {
|
||||
static serialize(obj: any, addNull?: boolean): string {
|
||||
let query = '',
|
||||
fullSubName,
|
||||
subValue,
|
||||
innerObj;
|
||||
|
||||
for (let name in obj) {
|
||||
let value = obj[name];
|
||||
for (const name in obj) {
|
||||
const value = obj[name];
|
||||
|
||||
if (value instanceof Array) {
|
||||
for (let i = 0; i < value.length; ++i) {
|
||||
|
@ -62,7 +64,7 @@ export class CoreInterceptor implements HttpInterceptor {
|
|||
query += this.serialize(innerObj) + '&';
|
||||
}
|
||||
} else if (value instanceof Object) {
|
||||
for (let subName in value) {
|
||||
for (const subName in value) {
|
||||
subValue = value[subName];
|
||||
fullSubName = name + '[' + subName + ']';
|
||||
innerObj = {};
|
||||
|
|
|
@ -107,7 +107,7 @@ export interface CoreSiteWSPreSets {
|
|||
* @type {string}
|
||||
*/
|
||||
typeExpected?: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Response of checking local_mobile status.
|
||||
|
@ -190,10 +190,10 @@ export class CoreSite {
|
|||
protected cleanUnicode = false;
|
||||
protected lastAutoLogin = 0;
|
||||
protected moodleReleases = {
|
||||
'3.1': 2016052300,
|
||||
'3.2': 2016120500,
|
||||
'3.3': 2017051503,
|
||||
'3.4': 2017111300
|
||||
3.1: 2016052300,
|
||||
3.2: 2016120500,
|
||||
3.3: 2017051503,
|
||||
3.4: 2017111300
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -211,7 +211,7 @@ export class CoreSite {
|
|||
constructor(injector: Injector, public id: string, public siteUrl: string, public token?: string, public infos?: any,
|
||||
public privateToken?: string, public config?: any, public loggedOut?: boolean) {
|
||||
// Inject the required services.
|
||||
let logger = injector.get(CoreLoggerProvider);
|
||||
const logger = injector.get(CoreLoggerProvider);
|
||||
this.appProvider = injector.get(CoreAppProvider);
|
||||
this.dbProvider = injector.get(CoreDbProvider);
|
||||
this.domUtils = injector.get(CoreDomUtilsProvider);
|
||||
|
@ -235,7 +235,7 @@ export class CoreSite {
|
|||
/**
|
||||
* Initialize the database.
|
||||
*/
|
||||
initDB() : void {
|
||||
initDB(): void {
|
||||
this.db = this.dbProvider.getDB('Site-' + this.id);
|
||||
this.db.createTableFromSchema(this.tableSchema);
|
||||
}
|
||||
|
@ -245,7 +245,7 @@ export class CoreSite {
|
|||
*
|
||||
* @return {string} Site ID.
|
||||
*/
|
||||
getId() : string {
|
||||
getId(): string {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
|
@ -254,7 +254,7 @@ export class CoreSite {
|
|||
*
|
||||
* @return {string} Site URL.
|
||||
*/
|
||||
getURL() : string {
|
||||
getURL(): string {
|
||||
return this.siteUrl;
|
||||
}
|
||||
|
||||
|
@ -263,7 +263,7 @@ export class CoreSite {
|
|||
*
|
||||
* @return {string} Site token.
|
||||
*/
|
||||
getToken() : string {
|
||||
getToken(): string {
|
||||
return this.token;
|
||||
}
|
||||
|
||||
|
@ -272,7 +272,7 @@ export class CoreSite {
|
|||
*
|
||||
* @return {any} Site info.
|
||||
*/
|
||||
getInfo() : any {
|
||||
getInfo(): any {
|
||||
return this.infos;
|
||||
}
|
||||
|
||||
|
@ -281,7 +281,7 @@ export class CoreSite {
|
|||
*
|
||||
* @return {string} Site private token.
|
||||
*/
|
||||
getPrivateToken() : string {
|
||||
getPrivateToken(): string {
|
||||
return this.privateToken;
|
||||
}
|
||||
|
||||
|
@ -290,7 +290,7 @@ export class CoreSite {
|
|||
*
|
||||
* @return {SQLiteDB} Site DB.
|
||||
*/
|
||||
getDb() : SQLiteDB {
|
||||
getDb(): SQLiteDB {
|
||||
return this.db;
|
||||
}
|
||||
|
||||
|
@ -299,7 +299,7 @@ export class CoreSite {
|
|||
*
|
||||
* @return {number} User's ID.
|
||||
*/
|
||||
getUserId() : number {
|
||||
getUserId(): number {
|
||||
if (typeof this.infos != 'undefined' && typeof this.infos.userid != 'undefined') {
|
||||
return this.infos.userid;
|
||||
}
|
||||
|
@ -310,7 +310,7 @@ export class CoreSite {
|
|||
*
|
||||
* @return {number} Site Home ID.
|
||||
*/
|
||||
getSiteHomeId() : number {
|
||||
getSiteHomeId(): number {
|
||||
return this.infos && this.infos.siteid || 1;
|
||||
}
|
||||
|
||||
|
@ -319,7 +319,7 @@ export class CoreSite {
|
|||
*
|
||||
* @param {string} New ID.
|
||||
*/
|
||||
setId(id: string) : void {
|
||||
setId(id: string): void {
|
||||
this.id = id;
|
||||
this.initDB();
|
||||
}
|
||||
|
@ -329,7 +329,7 @@ export class CoreSite {
|
|||
*
|
||||
* @param {string} New token.
|
||||
*/
|
||||
setToken(token: string) : void {
|
||||
setToken(token: string): void {
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
|
@ -338,7 +338,7 @@ export class CoreSite {
|
|||
*
|
||||
* @param {string} privateToken New private token.
|
||||
*/
|
||||
setPrivateToken(privateToken: string) : void {
|
||||
setPrivateToken(privateToken: string): void {
|
||||
this.privateToken = privateToken;
|
||||
}
|
||||
|
||||
|
@ -347,7 +347,7 @@ export class CoreSite {
|
|||
*
|
||||
* @return {boolean} Whether is logged out.
|
||||
*/
|
||||
isLoggedOut() : boolean {
|
||||
isLoggedOut(): boolean {
|
||||
return !!this.loggedOut;
|
||||
}
|
||||
|
||||
|
@ -356,7 +356,7 @@ export class CoreSite {
|
|||
*
|
||||
* @param {any} New info.
|
||||
*/
|
||||
setInfo(infos: any) : void {
|
||||
setInfo(infos: any): void {
|
||||
this.infos = infos;
|
||||
}
|
||||
|
||||
|
@ -365,7 +365,7 @@ export class CoreSite {
|
|||
*
|
||||
* @param {any} Config.
|
||||
*/
|
||||
setConfig(config: any) : void {
|
||||
setConfig(config: any): void {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
|
@ -374,7 +374,7 @@ export class CoreSite {
|
|||
*
|
||||
* @param {boolean} loggedOut True if logged out and needs to authenticate again, false otherwise.
|
||||
*/
|
||||
setLoggedOut(loggedOut: boolean) : void {
|
||||
setLoggedOut(loggedOut: boolean): void {
|
||||
this.loggedOut = !!loggedOut;
|
||||
}
|
||||
|
||||
|
@ -383,8 +383,9 @@ export class CoreSite {
|
|||
*
|
||||
* @return {boolean} Whether can access my files.
|
||||
*/
|
||||
canAccessMyFiles() : boolean {
|
||||
canAccessMyFiles(): boolean {
|
||||
const infos = this.getInfo();
|
||||
|
||||
return infos && (typeof infos.usercanmanageownfiles === 'undefined' || infos.usercanmanageownfiles);
|
||||
}
|
||||
|
||||
|
@ -393,8 +394,9 @@ export class CoreSite {
|
|||
*
|
||||
* @return {boolean} Whether can download files.
|
||||
*/
|
||||
canDownloadFiles() : boolean {
|
||||
canDownloadFiles(): boolean {
|
||||
const infos = this.getInfo();
|
||||
|
||||
return infos && infos.downloadfiles;
|
||||
}
|
||||
|
||||
|
@ -405,15 +407,15 @@ export class CoreSite {
|
|||
* @param {boolean} [whenUndefined=true] The value to return when the parameter is undefined.
|
||||
* @return {boolean} Whether can use advanced feature.
|
||||
*/
|
||||
canUseAdvancedFeature(feature: string, whenUndefined = true) : boolean {
|
||||
let infos = this.getInfo(),
|
||||
canUse = true;
|
||||
canUseAdvancedFeature(feature: string, whenUndefined: boolean = true): boolean {
|
||||
const infos = this.getInfo();
|
||||
let canUse = true;
|
||||
|
||||
if (typeof infos.advancedfeatures === 'undefined') {
|
||||
canUse = whenUndefined;
|
||||
} else {
|
||||
for (let i in infos.advancedfeatures) {
|
||||
let item = infos.advancedfeatures[i];
|
||||
for (const i in infos.advancedfeatures) {
|
||||
const item = infos.advancedfeatures[i];
|
||||
if (item.name === feature && parseInt(item.value, 10) === 0) {
|
||||
canUse = false;
|
||||
}
|
||||
|
@ -429,8 +431,9 @@ export class CoreSite {
|
|||
*
|
||||
* @return {boolean} Whether can upload files.
|
||||
*/
|
||||
canUploadFiles() : boolean {
|
||||
canUploadFiles(): boolean {
|
||||
const infos = this.getInfo();
|
||||
|
||||
return infos && infos.uploadfiles;
|
||||
}
|
||||
|
||||
|
@ -439,12 +442,12 @@ export class CoreSite {
|
|||
*
|
||||
* @return {Promise<any>} A promise to be resolved when the site info is retrieved.
|
||||
*/
|
||||
fetchSiteInfo() : Promise<any> {
|
||||
// get_site_info won't be cached.
|
||||
let preSets = {
|
||||
fetchSiteInfo(): Promise<any> {
|
||||
// The get_site_info WS call won't be cached.
|
||||
const preSets = {
|
||||
getFromCache: false,
|
||||
saveToCache: false
|
||||
}
|
||||
};
|
||||
|
||||
// Reset clean Unicode to check if it's supported again.
|
||||
this.cleanUnicode = false;
|
||||
|
@ -460,7 +463,7 @@ export class CoreSite {
|
|||
* @param {CoreSiteWSPreSets} [preSets] Extra options.
|
||||
* @return {Promise<any>} Promise resolved with the response, rejected with CoreWSError if it fails.
|
||||
*/
|
||||
read(method: string, data: any, preSets?: CoreSiteWSPreSets) : Promise<any> {
|
||||
read(method: string, data: any, preSets?: CoreSiteWSPreSets): Promise<any> {
|
||||
preSets = preSets || {};
|
||||
if (typeof preSets.getFromCache == 'undefined') {
|
||||
preSets.getFromCache = true;
|
||||
|
@ -468,6 +471,7 @@ export class CoreSite {
|
|||
if (typeof preSets.saveToCache == 'undefined') {
|
||||
preSets.saveToCache = true;
|
||||
}
|
||||
|
||||
return this.request(method, data, preSets);
|
||||
}
|
||||
|
||||
|
@ -479,7 +483,7 @@ export class CoreSite {
|
|||
* @param {CoreSiteWSPreSets} [preSets] Extra options.
|
||||
* @return {Promise<any>} Promise resolved with the response, rejected with CoreWSError if it fails.
|
||||
*/
|
||||
write(method: string, data: any, preSets?: CoreSiteWSPreSets) : Promise<any> {
|
||||
write(method: string, data: any, preSets?: CoreSiteWSPreSets): Promise<any> {
|
||||
preSets = preSets || {};
|
||||
if (typeof preSets.getFromCache == 'undefined') {
|
||||
preSets.getFromCache = false;
|
||||
|
@ -490,6 +494,7 @@ export class CoreSite {
|
|||
if (typeof preSets.emergencyCache == 'undefined') {
|
||||
preSets.emergencyCache = false;
|
||||
}
|
||||
|
||||
return this.request(method, data, preSets);
|
||||
}
|
||||
|
||||
|
@ -512,8 +517,8 @@ export class CoreSite {
|
|||
* This method is smart which means that it will try to map the method to a compatibility one if need be, usually this
|
||||
* means that it will fallback on the 'local_mobile_' prefixed function if it is available and the non-prefixed is not.
|
||||
*/
|
||||
request(method: string, data: any, preSets: CoreSiteWSPreSets, retrying?: boolean) : Promise<any> {
|
||||
let initialToken = this.token;
|
||||
request(method: string, data: any, preSets: CoreSiteWSPreSets, retrying?: boolean): Promise<any> {
|
||||
const initialToken = this.token;
|
||||
data = data || {};
|
||||
|
||||
// Check if the method is available, use a prefixed version if possible.
|
||||
|
@ -525,11 +530,12 @@ export class CoreSite {
|
|||
method = compatibilityMethod;
|
||||
} else {
|
||||
this.logger.error(`WS function '${method}' is not available, even in compatibility mode.`);
|
||||
|
||||
return Promise.reject(this.wsProvider.createFakeWSError('core.wsfunctionnotavailable', true));
|
||||
}
|
||||
}
|
||||
|
||||
let wsPreSets: CoreWSPreSets = {
|
||||
const wsPreSets: CoreWSPreSets = {
|
||||
wsToken: this.token,
|
||||
siteUrl: this.siteUrl,
|
||||
cleanUnicode: this.cleanUnicode,
|
||||
|
@ -564,12 +570,11 @@ export class CoreSite {
|
|||
this.saveToCache(method, data, response, preSets);
|
||||
}
|
||||
|
||||
// We pass back a clone of the original object, this may
|
||||
// prevent errors if in the callback the object is modified.
|
||||
// We pass back a clone of the original object, this may prevent errors if in the callback the object is modified.
|
||||
return this.utils.clone(response);
|
||||
}).catch((error) => {
|
||||
if (error.errorcode == 'invalidtoken' ||
|
||||
(error.errorcode == 'accessexception' && error.message.indexOf('Invalid token - token expired') > -1)) {
|
||||
(error.errorcode == 'accessexception' && error.message.indexOf('Invalid token - token expired') > -1)) {
|
||||
if (initialToken !== this.token && !retrying) {
|
||||
// Token has changed, retry with the new token.
|
||||
return this.request(method, data, preSets, true);
|
||||
|
@ -586,41 +591,49 @@ export class CoreSite {
|
|||
error.message = this.translate.instant('core.lostconnection');
|
||||
} else if (error.errorcode === 'userdeleted') {
|
||||
// User deleted, trigger event.
|
||||
this.eventsProvider.trigger(CoreEventsProvider.USER_DELETED, {params: data}, this.id);
|
||||
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, {}, 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, {}, 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, {}, this.id);
|
||||
error.message = this.translate.instant('core.sitepolicynotagreederror');
|
||||
|
||||
return Promise.reject(error);
|
||||
} else if (error.errorcode === 'dmlwriteexception' && this.textUtils.hasUnicodeData(data)) {
|
||||
if (!this.cleanUnicode) {
|
||||
// Try again cleaning unicode.
|
||||
this.cleanUnicode = true;
|
||||
|
||||
return this.request(method, data, preSets);
|
||||
}
|
||||
// This should not happen.
|
||||
error.message = this.translate.instant('core.unicodenotsupported');
|
||||
|
||||
return Promise.reject(error);
|
||||
} else if (typeof preSets.emergencyCache !== 'undefined' && !preSets.emergencyCache) {
|
||||
this.logger.debug(`WS call '${method}' failed. Emergency cache is forbidden, rejecting.`);
|
||||
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
this.logger.debug(`WS call '${method}' failed. Trying to use the emergency cache.`);
|
||||
preSets.omitExpires = true;
|
||||
preSets.getFromCache = true;
|
||||
|
||||
return this.getFromCache(method, data, preSets, true).catch(() => {
|
||||
return Promise.reject(error);
|
||||
});
|
||||
|
@ -635,7 +648,7 @@ export class CoreSite {
|
|||
* @param {boolean} [checkPrefix=true] When true also checks with the compatibility prefix.
|
||||
* @return {boolean} Whether the WS is available.
|
||||
*/
|
||||
wsAvailable(method: string, checkPrefix = true) : boolean {
|
||||
wsAvailable(method: string, checkPrefix: boolean = true): boolean {
|
||||
if (typeof this.infos == 'undefined') {
|
||||
return false;
|
||||
}
|
||||
|
@ -662,8 +675,8 @@ export class CoreSite {
|
|||
* @param {any} data Arguments to pass to the method.
|
||||
* @return {string} Cache ID.
|
||||
*/
|
||||
protected getCacheId(method: string, data: any) : string {
|
||||
return <string>Md5.hashAsciiStr(method + ':' + this.utils.sortAndStringify(data));
|
||||
protected getCacheId(method: string, data: any): string {
|
||||
return <string> Md5.hashAsciiStr(method + ':' + this.utils.sortAndStringify(data));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -675,32 +688,33 @@ export class CoreSite {
|
|||
* @param {boolean} emergency Whether it's an "emergency" cache call (WS call failed).
|
||||
* @return {Promise<any>} Promise resolved with the WS response.
|
||||
*/
|
||||
protected getFromCache(method: string, data: any, preSets: CoreSiteWSPreSets, emergency?: boolean) : Promise<any> {
|
||||
let id = this.getCacheId(method, data),
|
||||
promise;
|
||||
protected getFromCache(method: string, data: any, preSets: CoreSiteWSPreSets, emergency?: boolean): Promise<any> {
|
||||
const id = this.getCacheId(method, data);
|
||||
let promise;
|
||||
|
||||
if (!this.db || !preSets.getFromCache) {
|
||||
return Promise.reject(null);
|
||||
}
|
||||
|
||||
if (preSets.getCacheUsingCacheKey || (emergency && preSets.getEmergencyCacheUsingCacheKey)) {
|
||||
promise = this.db.getRecords(this.WS_CACHE_TABLE, {key: preSets.cacheKey}).then((entries) => {
|
||||
promise = this.db.getRecords(this.WS_CACHE_TABLE, { key: preSets.cacheKey }).then((entries) => {
|
||||
if (!entries.length) {
|
||||
// Cache key not found, get by params sent.
|
||||
return this.db.getRecord(this.WS_CACHE_TABLE, {id: id});
|
||||
return this.db.getRecord(this.WS_CACHE_TABLE, { id: id });
|
||||
} else if (entries.length > 1) {
|
||||
// More than one entry found. Search the one with same ID as this call.
|
||||
for (let i = 0, len = entries.length; i < len; i++) {
|
||||
let entry = entries[i];
|
||||
const entry = entries[i];
|
||||
if (entry.id == id) {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return entries[0];
|
||||
});
|
||||
} else {
|
||||
promise = this.db.getRecord(this.WS_CACHE_TABLE, {id: id});
|
||||
promise = this.db.getRecord(this.WS_CACHE_TABLE, { id: id });
|
||||
}
|
||||
|
||||
return promise.then((entry) => {
|
||||
|
@ -711,6 +725,7 @@ export class CoreSite {
|
|||
if (!preSets.omitExpires) {
|
||||
if (now > entry.expirationTime) {
|
||||
this.logger.debug('Cached element found, but it is expired');
|
||||
|
||||
return Promise.reject(null);
|
||||
}
|
||||
}
|
||||
|
@ -718,6 +733,7 @@ export class CoreSite {
|
|||
if (typeof entry != 'undefined' && typeof entry.data != 'undefined') {
|
||||
const expires = (entry.expirationTime - now) / 1000;
|
||||
this.logger.info(`Cached element found, id: ${id} expires in ${expires} seconds`);
|
||||
|
||||
return JSON.parse(entry.data);
|
||||
}
|
||||
|
||||
|
@ -734,14 +750,14 @@ export class CoreSite {
|
|||
* @param {CoreSiteWSPreSets} preSets Extra options.
|
||||
* @return {Promise<any>} Promise resolved when the response is saved.
|
||||
*/
|
||||
protected saveToCache(method: string, data: any, response: any, preSets: CoreSiteWSPreSets) : Promise<any> {
|
||||
let id = this.getCacheId(method, data),
|
||||
cacheExpirationTime = CoreConfigConstants.cache_expiration_time,
|
||||
promise,
|
||||
protected saveToCache(method: string, data: any, response: any, preSets: CoreSiteWSPreSets): Promise<any> {
|
||||
const id = this.getCacheId(method, data),
|
||||
entry: any = {
|
||||
id: id,
|
||||
data: JSON.stringify(response)
|
||||
}
|
||||
};
|
||||
let cacheExpirationTime = CoreConfigConstants.cache_expiration_time,
|
||||
promise;
|
||||
|
||||
if (!this.db) {
|
||||
return Promise.reject(null);
|
||||
|
@ -761,7 +777,8 @@ export class CoreSite {
|
|||
if (preSets.cacheKey) {
|
||||
entry.key = preSets.cacheKey;
|
||||
}
|
||||
return this.db.insertOrUpdateRecord(this.WS_CACHE_TABLE, entry, {id: id});
|
||||
|
||||
return this.db.insertOrUpdateRecord(this.WS_CACHE_TABLE, entry, { id: id });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -775,16 +792,16 @@ export class CoreSite {
|
|||
* @param {boolean} [allCacheKey] True to delete all entries with the cache key, false to delete only by ID.
|
||||
* @return {Promise<any>} Promise resolved when the entries are deleted.
|
||||
*/
|
||||
protected deleteFromCache(method: string, data: any, preSets: CoreSiteWSPreSets, allCacheKey?: boolean) : Promise<any> {
|
||||
protected deleteFromCache(method: string, data: any, preSets: CoreSiteWSPreSets, allCacheKey?: boolean): Promise<any> {
|
||||
const id = this.getCacheId(method, data);
|
||||
|
||||
if (!this.db) {
|
||||
return Promise.reject(null);
|
||||
} else {
|
||||
if (allCacheKey) {
|
||||
return this.db.deleteRecords(this.WS_CACHE_TABLE, {key: preSets.cacheKey});
|
||||
return this.db.deleteRecords(this.WS_CACHE_TABLE, { key: preSets.cacheKey });
|
||||
} else {
|
||||
return this.db.deleteRecords(this.WS_CACHE_TABLE, {id: id});
|
||||
return this.db.deleteRecords(this.WS_CACHE_TABLE, { id: id });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -797,7 +814,7 @@ export class CoreSite {
|
|||
* @param {Function} [onProgress] Function to call on progress.
|
||||
* @return {Promise<any>} Promise resolved when uploaded.
|
||||
*/
|
||||
uploadFile(filePath: string, options: CoreWSFileUploadOptions, onProgress?: (event: ProgressEvent) => any) : Promise<any> {
|
||||
uploadFile(filePath: string, options: CoreWSFileUploadOptions, onProgress?: (event: ProgressEvent) => any): Promise<any> {
|
||||
if (!options.fileArea) {
|
||||
options.fileArea = 'draft';
|
||||
}
|
||||
|
@ -813,13 +830,14 @@ export class CoreSite {
|
|||
*
|
||||
* @return {Promise<any>} Promise resolved when the cache entries are invalidated.
|
||||
*/
|
||||
invalidateWsCache() : Promise<any> {
|
||||
invalidateWsCache(): Promise<any> {
|
||||
if (!this.db) {
|
||||
return Promise.reject(null);
|
||||
}
|
||||
|
||||
this.logger.debug('Invalidate all the cache for site: ' + this.id);
|
||||
return this.db.updateRecords(this.WS_CACHE_TABLE, {expirationTime: 0});
|
||||
|
||||
return this.db.updateRecords(this.WS_CACHE_TABLE, { expirationTime: 0 });
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -828,7 +846,7 @@ export class CoreSite {
|
|||
* @param {string} key Key to search.
|
||||
* @return {Promise<any>} Promise resolved when the cache entries are invalidated.
|
||||
*/
|
||||
invalidateWsCacheForKey(key: string) : Promise<any> {
|
||||
invalidateWsCacheForKey(key: string): Promise<any> {
|
||||
if (!this.db) {
|
||||
return Promise.reject(null);
|
||||
}
|
||||
|
@ -837,7 +855,8 @@ export class CoreSite {
|
|||
}
|
||||
|
||||
this.logger.debug('Invalidate cache for key: ' + key);
|
||||
return this.db.updateRecords(this.WS_CACHE_TABLE, {expirationTime: 0}, {key: key});
|
||||
|
||||
return this.db.updateRecords(this.WS_CACHE_TABLE, { expirationTime: 0 }, { key: key });
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -846,7 +865,7 @@ export class CoreSite {
|
|||
* @param {string[]} keys Keys to search.
|
||||
* @return {Promise<any>} Promise resolved when the cache entries are invalidated.
|
||||
*/
|
||||
invalidateMultipleWsCacheForKey(keys: string[]) : Promise<any> {
|
||||
invalidateMultipleWsCacheForKey(keys: string[]): Promise<any> {
|
||||
if (!this.db) {
|
||||
return Promise.reject(null);
|
||||
}
|
||||
|
@ -854,7 +873,7 @@ export class CoreSite {
|
|||
return Promise.resolve();
|
||||
}
|
||||
|
||||
let promises = [];
|
||||
const promises = [];
|
||||
|
||||
this.logger.debug('Invalidating multiple cache keys');
|
||||
keys.forEach((key) => {
|
||||
|
@ -870,7 +889,7 @@ export class CoreSite {
|
|||
* @param {string} key Key to search.
|
||||
* @return {Promise} Promise resolved when the cache entries are invalidated.
|
||||
*/
|
||||
invalidateWsCacheForKeyStartingWith(key: string) : Promise<any> {
|
||||
invalidateWsCacheForKeyStartingWith(key: string): Promise<any> {
|
||||
if (!this.db) {
|
||||
return Promise.reject(null);
|
||||
}
|
||||
|
@ -879,8 +898,10 @@ 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 + "%"]);
|
||||
|
||||
const sql = 'UPDATE ' + this.WS_CACHE_TABLE + ' SET expirationTime=0 WHERE key LIKE ?';
|
||||
|
||||
return this.db.execute(sql, [key + '%']);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -890,7 +911,7 @@ export class CoreSite {
|
|||
* @param {string} url The url to be fixed.
|
||||
* @return {string} Fixed URL.
|
||||
*/
|
||||
fixPluginfileURL(url: string) : string {
|
||||
fixPluginfileURL(url: string): string {
|
||||
return this.urlUtils.fixPluginfileURL(url, this.token);
|
||||
}
|
||||
|
||||
|
@ -899,7 +920,7 @@ export class CoreSite {
|
|||
*
|
||||
* @return {Promise<any>} Promise to be resolved when the DB is deleted.
|
||||
*/
|
||||
deleteDB() : Promise<any> {
|
||||
deleteDB(): Promise<any> {
|
||||
return this.dbProvider.deleteDB('Site-' + this.id);
|
||||
}
|
||||
|
||||
|
@ -908,9 +929,10 @@ export class CoreSite {
|
|||
*
|
||||
* @return {Promise<any>} Promise to be resolved when the DB is deleted.
|
||||
*/
|
||||
deleteFolder() : Promise<any> {
|
||||
deleteFolder(): Promise<any> {
|
||||
if (this.fileProvider.isAvailable()) {
|
||||
const siteFolder = this.fileProvider.getSiteFolder(this.id);
|
||||
|
||||
return this.fileProvider.removeDir(siteFolder).catch(() => {
|
||||
// Ignore any errors, $mmFS.removeDir fails if folder doesn't exists.
|
||||
});
|
||||
|
@ -924,9 +946,10 @@ export class CoreSite {
|
|||
*
|
||||
* @return {Promise<number>} Promise resolved with the site space usage (size).
|
||||
*/
|
||||
getSpaceUsage() : Promise<number> {
|
||||
getSpaceUsage(): Promise<number> {
|
||||
if (this.fileProvider.isAvailable()) {
|
||||
const siteFolderPath = this.fileProvider.getSiteFolder(this.id);
|
||||
|
||||
return this.fileProvider.getDirectorySize(siteFolderPath).catch(() => {
|
||||
return 0;
|
||||
});
|
||||
|
@ -941,8 +964,9 @@ export class CoreSite {
|
|||
* @param {string} [page] Docs page to go to.
|
||||
* @return {Promise<string>} Promise resolved with the Moodle docs URL.
|
||||
*/
|
||||
getDocsUrl(page?: string) : Promise<string> {
|
||||
getDocsUrl(page?: string): Promise<string> {
|
||||
const release = this.infos.release ? this.infos.release : undefined;
|
||||
|
||||
return this.urlUtils.getDocsUrl(release, page);
|
||||
}
|
||||
|
||||
|
@ -952,27 +976,29 @@ export class CoreSite {
|
|||
* @param {boolean} [retrying] True if we're retrying the check.
|
||||
* @return {Promise<LocalMobileResponse>} Promise resolved when the check is done.
|
||||
*/
|
||||
checkLocalMobilePlugin(retrying?: boolean) : Promise<LocalMobileResponse> {
|
||||
checkLocalMobilePlugin(retrying?: boolean): Promise<LocalMobileResponse> {
|
||||
const checkUrl = this.siteUrl + '/local/mobile/check.php',
|
||||
service = CoreConfigConstants.wsextservice;
|
||||
|
||||
if (!service) {
|
||||
// External service not defined.
|
||||
return Promise.resolve({code: 0});
|
||||
return Promise.resolve({ code: 0 });
|
||||
}
|
||||
|
||||
let observable = this.http.post(checkUrl, {service: service}).timeout(CoreConstants.WS_TIMEOUT);
|
||||
const observable = this.http.post(checkUrl, { service: service }).timeout(CoreConstants.WS_TIMEOUT);
|
||||
|
||||
return this.utils.observableToPromise(observable).then((data: any) => {
|
||||
if (typeof data != 'undefined' && data.errorcode === 'requirecorrectaccess') {
|
||||
if (!retrying) {
|
||||
this.siteUrl = this.urlUtils.addOrRemoveWWW(this.siteUrl);
|
||||
|
||||
return this.checkLocalMobilePlugin(true);
|
||||
} else {
|
||||
return Promise.reject(data.error);
|
||||
}
|
||||
} else if (typeof data == 'undefined' || typeof data.code == 'undefined') {
|
||||
// local_mobile returned something we didn't expect. Let's assume it's not installed.
|
||||
return {code: 0, warning: 'core.login.localmobileunexpectedresponse'};
|
||||
} else if (typeof data == 'undefined' || typeof data.code == 'undefined') {
|
||||
// The local_mobile returned something we didn't expect. Let's assume it's not installed.
|
||||
return { code: 0, warning: 'core.login.localmobileunexpectedresponse' };
|
||||
}
|
||||
|
||||
const code = parseInt(data.code, 10);
|
||||
|
@ -986,7 +1012,7 @@ export class CoreSite {
|
|||
return Promise.reject(this.translate.instant('core.login.webservicesnotenabled'));
|
||||
case 3:
|
||||
// Extended service not enabled, but the official is enabled.
|
||||
return {code: 0};
|
||||
return { code: 0 };
|
||||
case 4:
|
||||
// Neither extended or official services enabled.
|
||||
return Promise.reject(this.translate.instant('core.login.mobileservicesnotenabled'));
|
||||
|
@ -994,10 +1020,10 @@ export class CoreSite {
|
|||
return Promise.reject(this.translate.instant('core.unexpectederror'));
|
||||
}
|
||||
} else {
|
||||
return {code: code, service: service, coresupported: !!data.coresupported};
|
||||
return { code: code, service: service, coresupported: !!data.coresupported };
|
||||
}
|
||||
}, () => {
|
||||
return {code: 0};
|
||||
return { code: 0 };
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1006,7 +1032,7 @@ export class CoreSite {
|
|||
*
|
||||
* @return {boolean} Whether the App is able to use local_mobile plugin for this site.
|
||||
*/
|
||||
checkIfAppUsesLocalMobile() : boolean {
|
||||
checkIfAppUsesLocalMobile(): boolean {
|
||||
let appUsesLocalMobile = false;
|
||||
|
||||
if (!this.infos || !this.infos.functions) {
|
||||
|
@ -1027,7 +1053,7 @@ export class CoreSite {
|
|||
*
|
||||
* @return {Promise<any>} Promise resolved it local_mobile was added, rejected otherwise.
|
||||
*/
|
||||
checkIfLocalMobileInstalledAndNotUsed() : Promise<any> {
|
||||
checkIfLocalMobileInstalledAndNotUsed(): Promise<any> {
|
||||
const appUsesLocalMobile = this.checkIfAppUsesLocalMobile();
|
||||
|
||||
if (appUsesLocalMobile) {
|
||||
|
@ -1035,11 +1061,12 @@ export class CoreSite {
|
|||
return Promise.reject(null);
|
||||
}
|
||||
|
||||
return this.checkLocalMobilePlugin().then((data: LocalMobileResponse) : any => {
|
||||
return this.checkLocalMobilePlugin().then((data: LocalMobileResponse): any => {
|
||||
if (typeof data.service == 'undefined') {
|
||||
// local_mobile NOT installed. Reject.
|
||||
// The local_mobile NOT installed. Reject.
|
||||
return Promise.reject(null);
|
||||
}
|
||||
|
||||
return data;
|
||||
});
|
||||
}
|
||||
|
@ -1050,13 +1077,14 @@ export class CoreSite {
|
|||
* @param {string} url URL to check.
|
||||
* @return {boolean} Whether the URL belongs to this site.
|
||||
*/
|
||||
containsUrl(url: string) : boolean {
|
||||
containsUrl(url: string): boolean {
|
||||
if (!url) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const siteUrl = this.urlUtils.removeProtocolAndWWW(this.siteUrl);
|
||||
url = this.urlUtils.removeProtocolAndWWW(url);
|
||||
|
||||
return url.indexOf(siteUrl) == 0;
|
||||
}
|
||||
|
||||
|
@ -1065,12 +1093,13 @@ export class CoreSite {
|
|||
*
|
||||
* @return {Promise<any>} Promise resolved with public config. Rejected with an object if error, see CoreWSProvider.callAjax.
|
||||
*/
|
||||
getPublicConfig() : Promise<any> {
|
||||
return this.wsProvider.callAjax('tool_mobile_get_public_config', {}, {siteUrl: this.siteUrl}).then((config) => {
|
||||
getPublicConfig(): Promise<any> {
|
||||
return this.wsProvider.callAjax('tool_mobile_get_public_config', {}, { siteUrl: this.siteUrl }).then((config) => {
|
||||
// Use the wwwroot returned by the server.
|
||||
if (config.httpswwwroot) {
|
||||
this.siteUrl = config.httpswwwroot;
|
||||
}
|
||||
|
||||
return config;
|
||||
});
|
||||
}
|
||||
|
@ -1082,7 +1111,7 @@ export class CoreSite {
|
|||
* @param {string} [alertMessage] If defined, an alert will be shown before opening the browser.
|
||||
* @return {Promise<any>} Promise resolved when done, rejected otherwise.
|
||||
*/
|
||||
openInBrowserWithAutoLogin(url: string, alertMessage?: string) : Promise<any> {
|
||||
openInBrowserWithAutoLogin(url: string, alertMessage?: string): Promise<any> {
|
||||
return this.openWithAutoLogin(false, url, undefined, alertMessage);
|
||||
}
|
||||
|
||||
|
@ -1093,7 +1122,7 @@ export class CoreSite {
|
|||
* @param {string} [alertMessage] If defined, an alert will be shown before opening the browser.
|
||||
* @return {Promise<any>} Promise resolved when done, rejected otherwise.
|
||||
*/
|
||||
openInBrowserWithAutoLoginIfSameSite(url: string, alertMessage?: string) : Promise<any> {
|
||||
openInBrowserWithAutoLoginIfSameSite(url: string, alertMessage?: string): Promise<any> {
|
||||
return this.openWithAutoLoginIfSameSite(false, url, undefined, alertMessage);
|
||||
}
|
||||
|
||||
|
@ -1105,7 +1134,7 @@ export class CoreSite {
|
|||
* @param {string} [alertMessage] If defined, an alert will be shown before opening the inappbrowser.
|
||||
* @return {Promise<InAppBrowserObject>} Promise resolved when done.
|
||||
*/
|
||||
openInAppWithAutoLogin(url: string, options?: any, alertMessage?: string) : Promise<InAppBrowserObject> {
|
||||
openInAppWithAutoLogin(url: string, options?: any, alertMessage?: string): Promise<InAppBrowserObject> {
|
||||
return this.openWithAutoLogin(true, url, options, alertMessage);
|
||||
}
|
||||
|
||||
|
@ -1117,7 +1146,7 @@ export class CoreSite {
|
|||
* @param {string} [alertMessage] If defined, an alert will be shown before opening the inappbrowser.
|
||||
* @return {Promise<InAppBrowserObject>} Promise resolved when done.
|
||||
*/
|
||||
openInAppWithAutoLoginIfSameSite(url: string, options?: any, alertMessage?: string) : Promise<InAppBrowserObject> {
|
||||
openInAppWithAutoLoginIfSameSite(url: string, options?: any, alertMessage?: string): Promise<InAppBrowserObject> {
|
||||
return this.openWithAutoLoginIfSameSite(true, url, options, alertMessage);
|
||||
}
|
||||
|
||||
|
@ -1130,16 +1159,16 @@ export class CoreSite {
|
|||
* @param {string} [alertMessage] If defined, an alert will be shown before opening the browser/inappbrowser.
|
||||
* @return {Promise<InAppBrowserObject>} Promise resolved when done. Resolve param is returned only if inApp=true.
|
||||
*/
|
||||
openWithAutoLogin(inApp: boolean, url: string, options?: any, alertMessage?: string) : Promise<InAppBrowserObject> {
|
||||
openWithAutoLogin(inApp: boolean, url: string, options?: any, alertMessage?: string): Promise<InAppBrowserObject> {
|
||||
// Convenience function to open the URL.
|
||||
let open = (url) => {
|
||||
return new Promise<InAppBrowserObject>((resolve, reject) => {
|
||||
const open = (url): Promise<any> => {
|
||||
return new Promise<InAppBrowserObject>((resolve, reject): void => {
|
||||
if (modal) {
|
||||
modal.dismiss();
|
||||
}
|
||||
|
||||
if (alertMessage) {
|
||||
let alert = this.domUtils.showAlert(this.translate.instant('core.notice'), alertMessage, undefined, 3000);
|
||||
const alert = this.domUtils.showAlert(this.translate.instant('core.notice'), alertMessage, undefined, 3000);
|
||||
alert.onDidDismiss(() => {
|
||||
if (inApp) {
|
||||
resolve(this.utils.openInApp(url, options));
|
||||
|
@ -1157,8 +1186,8 @@ export class CoreSite {
|
|||
});
|
||||
};
|
||||
|
||||
if (!this.privateToken || !this.wsAvailable('tool_mobile_get_autologin_key') ||
|
||||
(this.lastAutoLogin && this.timeUtils.timestamp() - this.lastAutoLogin < 6 * CoreConstants.SECONDS_MINUTE)) {
|
||||
if (!this.privateToken || !this.wsAvailable('tool_mobile_get_autologin_key') ||
|
||||
(this.lastAutoLogin && this.timeUtils.timestamp() - this.lastAutoLogin < CoreConstants.SECONDS_MINUTE * 6)) {
|
||||
// No private token, WS not available or last auto-login was less than 6 minutes ago.
|
||||
// Open the final URL without auto-login.
|
||||
return Promise.resolve(open(url));
|
||||
|
@ -1172,7 +1201,7 @@ export class CoreSite {
|
|||
|
||||
// Use write to not use cache.
|
||||
return this.write('tool_mobile_get_autologin_key', params).then((data) => {
|
||||
if (!data.autologinurl || !data.key) {
|
||||
if (!data.autologinurl || !data.key) {
|
||||
// Not valid data, open the final URL without auto-login.
|
||||
return open(url);
|
||||
}
|
||||
|
@ -1195,7 +1224,7 @@ export class CoreSite {
|
|||
* @param {string} [alertMessage] If defined, an alert will be shown before opening the browser/inappbrowser.
|
||||
* @return {Promise<InAppBrowserObject>} Promise resolved when done. Resolve param is returned only if inApp=true.
|
||||
*/
|
||||
openWithAutoLoginIfSameSite(inApp: boolean, url: string, options?: any, alertMessage?: string) : Promise<InAppBrowserObject> {
|
||||
openWithAutoLoginIfSameSite(inApp: boolean, url: string, options?: any, alertMessage?: string): Promise<InAppBrowserObject> {
|
||||
if (this.containsUrl(url)) {
|
||||
return this.openWithAutoLogin(inApp, url, options, alertMessage);
|
||||
} else {
|
||||
|
@ -1204,6 +1233,7 @@ export class CoreSite {
|
|||
} else {
|
||||
this.utils.openInBrowser(url);
|
||||
}
|
||||
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
}
|
||||
|
@ -1216,10 +1246,10 @@ export class CoreSite {
|
|||
* @param {boolean} [ignoreCache] True if it should ignore cached data.
|
||||
* @return {Promise<any>} Promise resolved with site config.
|
||||
*/
|
||||
getConfig(name?: string, ignoreCache?: boolean) {
|
||||
let preSets: CoreSiteWSPreSets = {
|
||||
getConfig(name?: string, ignoreCache?: boolean): Promise<any> {
|
||||
const preSets: CoreSiteWSPreSets = {
|
||||
cacheKey: this.getConfigCacheKey()
|
||||
}
|
||||
};
|
||||
|
||||
if (ignoreCache) {
|
||||
preSets.getFromCache = false;
|
||||
|
@ -1229,18 +1259,20 @@ export class CoreSite {
|
|||
return this.read('tool_mobile_get_config', {}, preSets).then((config) => {
|
||||
if (name) {
|
||||
// Return the requested setting.
|
||||
for (let x in config.settings) {
|
||||
for (const x in config.settings) {
|
||||
if (config.settings[x].name == name) {
|
||||
return config.settings[x].value;
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.reject(null);
|
||||
} else {
|
||||
// Return all settings in the same array.
|
||||
let settings = {};
|
||||
const settings = {};
|
||||
config.settings.forEach((setting) => {
|
||||
settings[setting.name] = setting.value;
|
||||
});
|
||||
|
||||
return settings;
|
||||
}
|
||||
});
|
||||
|
@ -1251,7 +1283,7 @@ export class CoreSite {
|
|||
*
|
||||
* @return {Promise<any>} Promise resolved when the data is invalidated.
|
||||
*/
|
||||
invalidateConfig() : Promise<any> {
|
||||
invalidateConfig(): Promise<any> {
|
||||
return this.invalidateWsCacheForKey(this.getConfigCacheKey());
|
||||
}
|
||||
|
||||
|
@ -1260,7 +1292,7 @@ export class CoreSite {
|
|||
*
|
||||
* @return {string} Cache key.
|
||||
*/
|
||||
protected getConfigCacheKey() : string {
|
||||
protected getConfigCacheKey(): string {
|
||||
return 'tool_mobile_get_config';
|
||||
}
|
||||
|
||||
|
@ -1270,7 +1302,7 @@ export class CoreSite {
|
|||
* @param {string} [name] Name of the setting to get. If not set, all settings will be returned.
|
||||
* @return {any} Site config or a specific setting.
|
||||
*/
|
||||
getStoredConfig(name?: string) : any {
|
||||
getStoredConfig(name?: string): any {
|
||||
if (!this.config) {
|
||||
return;
|
||||
}
|
||||
|
@ -1288,13 +1320,14 @@ export class CoreSite {
|
|||
* @param {string} name Name of the feature to check.
|
||||
* @return {boolean} Whether it's disabled.
|
||||
*/
|
||||
isFeatureDisabled(name: string) : boolean {
|
||||
isFeatureDisabled(name: string): boolean {
|
||||
const disabledFeatures = this.getStoredConfig('tool_mobile_disabledfeatures');
|
||||
if (!disabledFeatures) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const regEx = new RegExp('(,|^)' + this.textUtils.escapeForRegex(name) + '(,|$)', 'g');
|
||||
|
||||
return !!disabledFeatures.match(regEx);
|
||||
}
|
||||
|
||||
|
@ -1319,7 +1352,7 @@ export class CoreSite {
|
|||
* This function only accepts versions from 2.4.0 and above. If any of the versions supplied isn't found, it will assume
|
||||
* it's the last released major version.
|
||||
*/
|
||||
isVersionGreaterEqualThan(versions: string | string[]) : boolean {
|
||||
isVersionGreaterEqualThan(versions: string | string[]): boolean {
|
||||
const siteVersion = parseInt(this.getInfo().version, 10);
|
||||
|
||||
if (Array.isArray(versions)) {
|
||||
|
@ -1328,7 +1361,7 @@ export class CoreSite {
|
|||
}
|
||||
|
||||
for (let i = 0; i < versions.length; i++) {
|
||||
let versionNumber = this.getVersionNumber(versions[i]);
|
||||
const versionNumber = this.getVersionNumber(versions[i]);
|
||||
if (i == versions.length - 1) {
|
||||
// It's the last version, check only if site version is greater than this one.
|
||||
return siteVersion >= versionNumber;
|
||||
|
@ -1354,8 +1387,8 @@ export class CoreSite {
|
|||
* @param {string} version Release version to convert to version number.
|
||||
* @return {number} Version number, 0 if invalid.
|
||||
*/
|
||||
protected getVersionNumber(version: string) : number {
|
||||
let data = this.getMajorAndMinor(version);
|
||||
protected getVersionNumber(version: string): number {
|
||||
const data = this.getMajorAndMinor(version);
|
||||
|
||||
if (!data) {
|
||||
// Invalid version.
|
||||
|
@ -1376,17 +1409,17 @@ export class CoreSite {
|
|||
* @param {string} version Release version (e.g. '3.1.0').
|
||||
* @return {object} Object with major and minor. Returns false if invalid version.
|
||||
*/
|
||||
protected getMajorAndMinor(version) : any {
|
||||
protected getMajorAndMinor(version: string): any {
|
||||
const match = version.match(/(\d)+(?:\.(\d)+)?(?:\.(\d)+)?/);
|
||||
if (!match || !match[1]) {
|
||||
if (!match || !match[1]) {
|
||||
// Invalid version.
|
||||
return false;
|
||||
}
|
||||
|
||||
return {
|
||||
major: match[1] + '.' + (match[2] || '0'),
|
||||
minor: parseInt(match[3] || 0, 10)
|
||||
}
|
||||
major: match[1] + '.' + (match[2] || '0'),
|
||||
minor: parseInt(match[3], 10) || 0
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1395,10 +1428,10 @@ export class CoreSite {
|
|||
* @param {string} version Release version (e.g. '3.1.0').
|
||||
* @return {number} Next major version number.
|
||||
*/
|
||||
protected getNextMajorVersionNumber(version: string) : number {
|
||||
let data = this.getMajorAndMinor(version),
|
||||
position,
|
||||
protected getNextMajorVersionNumber(version: string): number {
|
||||
const data = this.getMajorAndMinor(version),
|
||||
releases = Object.keys(this.moodleReleases);
|
||||
let position;
|
||||
|
||||
if (!data) {
|
||||
// Invalid version.
|
||||
|
@ -1407,7 +1440,7 @@ export class CoreSite {
|
|||
|
||||
position = releases.indexOf(data.major);
|
||||
|
||||
if (position == -1 || position == releases.length -1) {
|
||||
if (position == -1 || position == releases.length - 1) {
|
||||
// Major version not found or it's the last one. Use the last one.
|
||||
return this.moodleReleases[releases[position]];
|
||||
}
|
||||
|
|
|
@ -63,14 +63,14 @@ export class SQLiteDB {
|
|||
* @return SQL query.
|
||||
*/
|
||||
buildCreateTableSql(name: string, columns: any[], primaryKeys?: string[], uniqueKeys?: string[][], foreignKeys?: any[],
|
||||
tableCheck?: string) : string {
|
||||
let sql = `CREATE TABLE IF NOT EXISTS ${name} (`,
|
||||
columnsSql = [];
|
||||
tableCheck?: string): string {
|
||||
const columnsSql = [];
|
||||
let sql = `CREATE TABLE IF NOT EXISTS ${name} (`;
|
||||
|
||||
// First define all the columns.
|
||||
for (let index in columns) {
|
||||
let column = columns[index],
|
||||
columnSql: string = column.name || '';
|
||||
for (const index in columns) {
|
||||
const column = columns[index];
|
||||
let columnSql: string = column.name || '';
|
||||
|
||||
if (column.type) {
|
||||
columnSql += ' ' + column.type;
|
||||
|
@ -110,8 +110,8 @@ export class SQLiteDB {
|
|||
}
|
||||
|
||||
if (uniqueKeys && uniqueKeys.length) {
|
||||
for (let index in uniqueKeys) {
|
||||
let setOfKeys = uniqueKeys[index];
|
||||
for (const index in uniqueKeys) {
|
||||
const setOfKeys = uniqueKeys[index];
|
||||
if (setOfKeys && setOfKeys.length) {
|
||||
sql += `, UNIQUE (${setOfKeys.join(', ')})`;
|
||||
}
|
||||
|
@ -122,9 +122,8 @@ export class SQLiteDB {
|
|||
sql += `, CHECK (${tableCheck})`;
|
||||
}
|
||||
|
||||
|
||||
for (let index in foreignKeys) {
|
||||
let foreignKey = foreignKeys[index];
|
||||
for (const index in foreignKeys) {
|
||||
const foreignKey = foreignKeys[index];
|
||||
|
||||
if (!foreignKey.columns || !!foreignKey.columns.length) {
|
||||
return;
|
||||
|
@ -146,8 +145,10 @@ export class SQLiteDB {
|
|||
|
||||
/**
|
||||
* Close the database.
|
||||
*
|
||||
* @return {Promise<any>} Promise resolved when done.
|
||||
*/
|
||||
close() {
|
||||
close(): Promise<any> {
|
||||
return this.ready().then(() => {
|
||||
return this.db.close();
|
||||
});
|
||||
|
@ -160,8 +161,9 @@ export class SQLiteDB {
|
|||
* @param {object} [conditions] The conditions to build the where clause. Must not contain numeric indexes.
|
||||
* @return {Promise<number>} Promise resolved with the count of records returned from the specified criteria.
|
||||
*/
|
||||
countRecords(table: string, conditions?: object) : Promise<number> {
|
||||
let selectAndParams = this.whereClause(conditions);
|
||||
countRecords(table: string, conditions?: object): Promise<number> {
|
||||
const selectAndParams = this.whereClause(conditions);
|
||||
|
||||
return this.countRecordsSelect(table, selectAndParams[0], selectAndParams[1]);
|
||||
}
|
||||
|
||||
|
@ -174,10 +176,11 @@ export class SQLiteDB {
|
|||
* @param {string} [countItem] The count string to be used in the SQL call. Default is COUNT('x').
|
||||
* @return {Promise<number>} Promise resolved with the count of records returned from the specified criteria.
|
||||
*/
|
||||
countRecordsSelect(table: string, select='', params?: any, countItem="COUNT('x')") : Promise<number> {
|
||||
countRecordsSelect(table: string, select: string = '', params?: any, countItem: string = 'COUNT(\'x\')'): Promise<number> {
|
||||
if (select) {
|
||||
select = 'WHERE ' + select;
|
||||
}
|
||||
|
||||
return this.countRecordsSql(`SELECT ${countItem} FROM ${table} ${select}`, params);
|
||||
}
|
||||
|
||||
|
@ -190,11 +193,12 @@ export class SQLiteDB {
|
|||
* @param {any} [params] An array of sql parameters.
|
||||
* @return {Promise<number>} Promise resolved with the count.
|
||||
*/
|
||||
countRecordsSql(sql: string, params?: any) : Promise<number> {
|
||||
countRecordsSql(sql: string, params?: any): Promise<number> {
|
||||
return this.getFieldSql(sql, params).then((count) => {
|
||||
if (typeof count != 'number' || count < 0) {
|
||||
if (typeof count != 'number' || count < 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return count;
|
||||
});
|
||||
}
|
||||
|
@ -223,8 +227,9 @@ export class SQLiteDB {
|
|||
* @return {Promise<any>} Promise resolved when success.
|
||||
*/
|
||||
createTable(name: string, columns: any[], primaryKeys?: string[], uniqueKeys?: string[][], foreignKeys?: any[],
|
||||
tableCheck?: string) : Promise<any> {
|
||||
let sql = this.buildCreateTableSql(name, columns, primaryKeys, uniqueKeys, foreignKeys, tableCheck);
|
||||
tableCheck?: string): Promise<any> {
|
||||
const sql = this.buildCreateTableSql(name, columns, primaryKeys, uniqueKeys, foreignKeys, tableCheck);
|
||||
|
||||
return this.execute(sql);
|
||||
}
|
||||
|
||||
|
@ -234,9 +239,9 @@ export class SQLiteDB {
|
|||
* @param {any} table Table schema.
|
||||
* @return {Promise<any>} Promise resolved when success.
|
||||
*/
|
||||
createTableFromSchema(table: any) : Promise<any> {
|
||||
createTableFromSchema(table: any): Promise<any> {
|
||||
return this.createTable(table.name, table.columns, table.primaryKeys, table.uniqueKeys,
|
||||
table.foreignKeys, table.tableCheck);
|
||||
table.foreignKeys, table.tableCheck);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -245,11 +250,12 @@ export class SQLiteDB {
|
|||
* @param {any[]} tables List of table schema.
|
||||
* @return {Promise<any>} Promise resolved when success.
|
||||
*/
|
||||
createTablesFromSchema(tables: any[]) : Promise<any> {
|
||||
let promises = [];
|
||||
createTablesFromSchema(tables: any[]): Promise<any> {
|
||||
const promises = [];
|
||||
tables.forEach((table) => {
|
||||
promises.push(this.createTableFromSchema(table));
|
||||
});
|
||||
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
|
@ -261,13 +267,14 @@ export class SQLiteDB {
|
|||
* @param {object} [conditions] The conditions to build the where clause. Must not contain numeric indexes.
|
||||
* @return {Promise<any>} Promise resolved when done.
|
||||
*/
|
||||
deleteRecords(table: string, conditions?: object) : Promise<any> {
|
||||
deleteRecords(table: string, conditions?: object): Promise<any> {
|
||||
if (conditions === null || typeof conditions == 'undefined') {
|
||||
// No conditions, delete the whole table.
|
||||
return this.execute(`DELETE FROM TABLE ${table}`);
|
||||
}
|
||||
|
||||
let selectAndParams = this.whereClause(conditions);
|
||||
const selectAndParams = this.whereClause(conditions);
|
||||
|
||||
return this.deleteRecordsSelect(table, selectAndParams[0], selectAndParams[1]);
|
||||
}
|
||||
|
||||
|
@ -279,8 +286,9 @@ export class SQLiteDB {
|
|||
* @param {any[]} values The values field might take.
|
||||
* @return {Promise<any>} Promise resolved when done.
|
||||
*/
|
||||
deleteRecordsList(table: string, field: string, values: any[]) : Promise<any> {
|
||||
let selectAndParams = this.whereClauseList(field, values);
|
||||
deleteRecordsList(table: string, field: string, values: any[]): Promise<any> {
|
||||
const selectAndParams = this.whereClauseList(field, values);
|
||||
|
||||
return this.deleteRecordsSelect(table, selectAndParams[0], selectAndParams[1]);
|
||||
}
|
||||
|
||||
|
@ -292,7 +300,7 @@ export class SQLiteDB {
|
|||
* @param {any[]} [params] Array of sql parameters.
|
||||
* @return {Promise<any>} Promise resolved when done.
|
||||
*/
|
||||
deleteRecordsSelect(table: string, select='', params?: any[]) : Promise<any> {
|
||||
deleteRecordsSelect(table: string, select: string = '', params?: any[]): Promise<any> {
|
||||
if (select) {
|
||||
select = 'WHERE ' + select;
|
||||
}
|
||||
|
@ -309,7 +317,7 @@ export class SQLiteDB {
|
|||
* @param {any[]} params Query parameters.
|
||||
* @return {Promise<any>} Promise resolved with the result.
|
||||
*/
|
||||
execute(sql: string, params?: any[]) : Promise<any> {
|
||||
execute(sql: string, params?: any[]): Promise<any> {
|
||||
return this.ready().then(() => {
|
||||
return this.db.executeSql(sql, params);
|
||||
});
|
||||
|
@ -323,7 +331,7 @@ export class SQLiteDB {
|
|||
* @param {any[]} sqlStatements SQL statements to execute.
|
||||
* @return {Promise<any>} Promise resolved with the result.
|
||||
*/
|
||||
executeBatch(sqlStatements: any[]) : Promise<any> {
|
||||
executeBatch(sqlStatements: any[]): Promise<any> {
|
||||
return this.ready().then(() => {
|
||||
return this.db.sqlBatch(sqlStatements);
|
||||
});
|
||||
|
@ -334,10 +342,10 @@ export class SQLiteDB {
|
|||
*
|
||||
* @param {object} data Data to insert.
|
||||
*/
|
||||
protected formatDataToInsert(data: object) : void {
|
||||
protected formatDataToInsert(data: object): void {
|
||||
// Remove undefined entries and convert null to "NULL".
|
||||
for (let name in data) {
|
||||
let value = data[name];
|
||||
for (const name in data) {
|
||||
const value = data[name];
|
||||
if (typeof value == 'undefined') {
|
||||
delete data[name];
|
||||
}
|
||||
|
@ -350,7 +358,7 @@ export class SQLiteDB {
|
|||
* @param {string} table The table to query.
|
||||
* @return {Promise<any>} Promise resolved with the records.
|
||||
*/
|
||||
getAllRecords(table: string) : Promise<any> {
|
||||
getAllRecords(table: string): Promise<any> {
|
||||
return this.getRecords(table);
|
||||
}
|
||||
|
||||
|
@ -362,8 +370,9 @@ export class SQLiteDB {
|
|||
* @param {object} [conditions] The conditions to build the where clause. Must not contain numeric indexes.
|
||||
* @return {Promise<any>} Promise resolved with the field's value.
|
||||
*/
|
||||
getField(table: string, field: string, conditions?: object) : Promise<any> {
|
||||
let selectAndParams = this.whereClause(conditions);
|
||||
getField(table: string, field: string, conditions?: object): Promise<any> {
|
||||
const selectAndParams = this.whereClause(conditions);
|
||||
|
||||
return this.getFieldSelect(table, field, selectAndParams[0], selectAndParams[1]);
|
||||
}
|
||||
|
||||
|
@ -376,7 +385,7 @@ export class SQLiteDB {
|
|||
* @param {any[]} [params] Array of sql parameters.
|
||||
* @return {Promise<any>} Promise resolved with the field's value.
|
||||
*/
|
||||
getFieldSelect(table: string, field: string, select='', params?: any[]) : Promise<any> {
|
||||
getFieldSelect(table: string, field: string, select: string = '', params?: any[]): Promise<any> {
|
||||
if (select) {
|
||||
select = 'WHERE ' + select;
|
||||
}
|
||||
|
@ -391,7 +400,7 @@ export class SQLiteDB {
|
|||
* @param {any[]} [params] An array of sql parameters.
|
||||
* @return {Promise<any>} Promise resolved with the field's value.
|
||||
*/
|
||||
getFieldSql(sql: string, params?: any[]) : Promise<any> {
|
||||
getFieldSql(sql: string, params?: any[]): Promise<any> {
|
||||
return this.getRecordSql(sql, params).then((record) => {
|
||||
if (!record) {
|
||||
return Promise.reject(null);
|
||||
|
@ -411,7 +420,7 @@ export class SQLiteDB {
|
|||
* meaning return empty. Other values will become part of the returned SQL fragment.
|
||||
* @return {any[]} A list containing the constructed sql fragment and an array of parameters.
|
||||
*/
|
||||
getInOrEqual(items: any, equal=true, onEmptyItems?: any) : any[] {
|
||||
getInOrEqual(items: any, equal: boolean = true, onEmptyItems?: any): any[] {
|
||||
let sql,
|
||||
params;
|
||||
|
||||
|
@ -428,6 +437,7 @@ export class SQLiteDB {
|
|||
if (Array.isArray(items) && !items.length) {
|
||||
if (onEmptyItems === null) { // Special case, NULL value.
|
||||
sql = equal ? ' IS NULL' : ' IS NOT NULL';
|
||||
|
||||
return [sql, []];
|
||||
} else {
|
||||
items = [onEmptyItems]; // Rest of cases, prepare items for processing.
|
||||
|
@ -438,7 +448,7 @@ export class SQLiteDB {
|
|||
sql = equal ? '= ?' : '<> ?';
|
||||
params = Array.isArray(items) ? items : [items];
|
||||
} else {
|
||||
sql = (equal ? '' : 'NOT ') + 'IN (' + ',?'.repeat(items.length).substr(1) + ')';
|
||||
sql = (equal ? '' : 'NOT ') + 'IN (' + ',?'.repeat(items.length).substr(1) + ')';
|
||||
params = items;
|
||||
}
|
||||
|
||||
|
@ -450,7 +460,7 @@ export class SQLiteDB {
|
|||
*
|
||||
* @return {string} Database name.
|
||||
*/
|
||||
getName() : string {
|
||||
getName(): string {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
|
@ -462,8 +472,9 @@ export class SQLiteDB {
|
|||
* @param {string} [fields='*'] A comma separated list of fields to return.
|
||||
* @return {Promise<any>} Promise resolved with the record, rejected if not found.
|
||||
*/
|
||||
getRecord(table: string, conditions?: object, fields='*') : Promise<any> {
|
||||
let selectAndParams = this.whereClause(conditions);
|
||||
getRecord(table: string, conditions?: object, fields: string = '*'): Promise<any> {
|
||||
const selectAndParams = this.whereClause(conditions);
|
||||
|
||||
return this.getRecordSelect(table, selectAndParams[0], selectAndParams[1], fields);
|
||||
}
|
||||
|
||||
|
@ -476,7 +487,7 @@ export class SQLiteDB {
|
|||
* @param {string} [fields='*'] A comma separated list of fields to return.
|
||||
* @return {Promise<any>} Promise resolved with the record, rejected if not found.
|
||||
*/
|
||||
getRecordSelect(table: string, select='', params=[], fields='*') : Promise<any> {
|
||||
getRecordSelect(table: string, select: string = '', params: any[] = [], fields: string = '*'): Promise<any> {
|
||||
if (select) {
|
||||
select = ' WHERE ' + select;
|
||||
}
|
||||
|
@ -494,7 +505,7 @@ export class SQLiteDB {
|
|||
* @param {any[]} [params] List of sql parameters
|
||||
* @return {Promise<any>} Promise resolved with the records.
|
||||
*/
|
||||
getRecordSql(sql: string, params?: any[]) : Promise<any> {
|
||||
getRecordSql(sql: string, params?: any[]): Promise<any> {
|
||||
return this.getRecordsSql(sql, params, 0, 1).then((result) => {
|
||||
if (!result || !result.length) {
|
||||
// Not found, reject.
|
||||
|
@ -517,8 +528,10 @@ export class SQLiteDB {
|
|||
* @param {number} [limitNum=0] Return a subset comprising this many records in total.
|
||||
* @return {Promise<any>} Promise resolved with the records.
|
||||
*/
|
||||
getRecords(table: string, conditions?: object, sort='', fields='*', limitFrom=0, limitNum=0) : Promise<any> {
|
||||
let selectAndParams = this.whereClause(conditions);
|
||||
getRecords(table: string, conditions?: object, sort: string = '', fields: string = '*', limitFrom: number = 0,
|
||||
limitNum: number = 0): Promise<any> {
|
||||
const selectAndParams = this.whereClause(conditions);
|
||||
|
||||
return this.getRecordsSelect(table, selectAndParams[0], selectAndParams[1], sort, fields, limitFrom, limitNum);
|
||||
}
|
||||
|
||||
|
@ -534,8 +547,10 @@ export class SQLiteDB {
|
|||
* @param {number} [limitNum=0] Return a subset comprising this many records in total.
|
||||
* @return {Promise<any>} Promise resolved with the records.
|
||||
*/
|
||||
getRecordsList(table: string, field: string, values: any[], sort='', fields='*', limitFrom=0, limitNum=0) : Promise<any> {
|
||||
let selectAndParams = this.whereClauseList(field, values);
|
||||
getRecordsList(table: string, field: string, values: any[], sort: string = '', fields: string = '*', limitFrom: number = 0,
|
||||
limitNum: number = 0): Promise<any> {
|
||||
const selectAndParams = this.whereClauseList(field, values);
|
||||
|
||||
return this.getRecordsSelect(table, selectAndParams[0], selectAndParams[1], sort, fields, limitFrom, limitNum);
|
||||
}
|
||||
|
||||
|
@ -551,7 +566,8 @@ export class SQLiteDB {
|
|||
* @param {number} [limitNum=0] Return a subset comprising this many records in total.
|
||||
* @return {Promise<any>} Promise resolved with the records.
|
||||
*/
|
||||
getRecordsSelect(table: string, select='', params=[], sort='', fields='*', limitFrom=0, limitNum=0) : Promise<any> {
|
||||
getRecordsSelect(table: string, select: string = '', params: any[] = [], sort: string = '', fields: string = '*',
|
||||
limitFrom: number = 0, limitNum: number = 0): Promise<any> {
|
||||
if (select) {
|
||||
select = ' WHERE ' + select;
|
||||
}
|
||||
|
@ -559,7 +575,8 @@ export class SQLiteDB {
|
|||
sort = ' ORDER BY ' + sort;
|
||||
}
|
||||
|
||||
let sql = `SELECT ${fields} FROM ${table} ${select} ${sort}`;
|
||||
const sql = `SELECT ${fields} FROM ${table} ${select} ${sort}`;
|
||||
|
||||
return this.getRecordsSql(sql, params, limitFrom, limitNum);
|
||||
}
|
||||
|
||||
|
@ -572,8 +589,8 @@ export class SQLiteDB {
|
|||
* @param {number} [limitNum] Return a subset comprising this many records.
|
||||
* @return {Promise<any>} Promise resolved with the records.
|
||||
*/
|
||||
getRecordsSql(sql: string, params?: any[], limitFrom?: number, limitNum?: number) : Promise<any> {
|
||||
let limits = this.normaliseLimitFromNum(limitFrom, limitNum);
|
||||
getRecordsSql(sql: string, params?: any[], limitFrom?: number, limitNum?: number): Promise<any> {
|
||||
const limits = this.normaliseLimitFromNum(limitFrom, limitNum);
|
||||
|
||||
if (limits[0] || limits[1]) {
|
||||
if (limits[1] < 1) {
|
||||
|
@ -584,10 +601,11 @@ export class SQLiteDB {
|
|||
|
||||
return this.execute(sql, params).then((result) => {
|
||||
// Retrieve the records.
|
||||
let records = [];
|
||||
const records = [];
|
||||
for (let i = 0; i < result.rows.length; i++) {
|
||||
records.push(result.rows.item(i));
|
||||
}
|
||||
|
||||
return records;
|
||||
});
|
||||
}
|
||||
|
@ -599,23 +617,23 @@ export class SQLiteDB {
|
|||
* @param {object} data A data object with values for one or more fields in the record.
|
||||
* @return {any[]} Array with the SQL query and the params.
|
||||
*/
|
||||
protected getSqlInsertQuery(table: string, data: object) : any[] {
|
||||
protected getSqlInsertQuery(table: string, data: object): any[] {
|
||||
this.formatDataToInsert(data);
|
||||
|
||||
let keys = Object.keys(data),
|
||||
const keys = Object.keys(data),
|
||||
fields = keys.join(','),
|
||||
questionMarks = ',?'.repeat(keys.length).substr(1);
|
||||
|
||||
return [
|
||||
`INSERT INTO ${table} (${fields}) VALUES (${questionMarks})`,
|
||||
keys.map(key => data[key])
|
||||
keys.map((key) => data[key])
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the database.
|
||||
*/
|
||||
init() : void {
|
||||
init(): void {
|
||||
this.promise = this.platform.ready().then(() => {
|
||||
return this.sqlite.create({
|
||||
name: this.name,
|
||||
|
@ -634,8 +652,8 @@ export class SQLiteDB {
|
|||
* @param {object} conditions The conditions to check if the record already exists.
|
||||
* @return {Promise<any>} Promise resolved with done.
|
||||
*/
|
||||
insertOrUpdateRecord(table: string, data: object, conditions: object) : Promise<any> {
|
||||
return this.getRecord(table, conditions || data).then(() => {
|
||||
insertOrUpdateRecord(table: string, data: object, conditions: object): Promise<any> {
|
||||
return this.getRecord(table, conditions || data).then(() => {
|
||||
// It exists, update it.
|
||||
return this.updateRecords(table, data, conditions);
|
||||
}).catch(() => {
|
||||
|
@ -651,8 +669,8 @@ export class SQLiteDB {
|
|||
* @param {object} data A data object with values for one or more fields in the record.
|
||||
* @return {Promise<number>} Promise resolved with new rowId. Please notice this rowId is internal from SQLite.
|
||||
*/
|
||||
insertRecord(table: string, data: object) : Promise<number> {
|
||||
let sqlAndParams = this.getSqlInsertQuery(table, data);
|
||||
insertRecord(table: string, data: object): Promise<number> {
|
||||
const sqlAndParams = this.getSqlInsertQuery(table, data);
|
||||
|
||||
return this.execute(sqlAndParams[0], sqlAndParams[1]).then((result) => {
|
||||
return result.insertId;
|
||||
|
@ -666,12 +684,12 @@ export class SQLiteDB {
|
|||
* @param {object[]} dataObjects List of objects to be inserted.
|
||||
* @return {Promise<any>} Promise resolved when done.
|
||||
*/
|
||||
insertRecords(table: string, dataObjects: object[]) : Promise<any> {
|
||||
insertRecords(table: string, dataObjects: object[]): Promise<any> {
|
||||
if (!Array.isArray(dataObjects)) {
|
||||
return Promise.reject(null);
|
||||
}
|
||||
|
||||
let statements = [];
|
||||
const statements = [];
|
||||
|
||||
dataObjects.forEach((dataObject) => {
|
||||
statements.push(this.getSqlInsertQuery(table, dataObject));
|
||||
|
@ -689,7 +707,7 @@ export class SQLiteDB {
|
|||
* @param {any} limitNum How many results to return.
|
||||
* @return {number[]} Normalised limit params in array: [limitFrom, limitNum].
|
||||
*/
|
||||
normaliseLimitFromNum(limitFrom: any, limitNum: any) : number[] {
|
||||
normaliseLimitFromNum(limitFrom: any, limitNum: any): number[] {
|
||||
// We explicilty treat these cases as 0.
|
||||
if (typeof limitFrom == 'undefined' || limitFrom === null || limitFrom === '' || limitFrom === -1) {
|
||||
limitFrom = 0;
|
||||
|
@ -699,17 +717,19 @@ export class SQLiteDB {
|
|||
}
|
||||
|
||||
limitFrom = parseInt(limitFrom, 10);
|
||||
limitNum = parseInt(limitNum, 10);
|
||||
limitNum = parseInt(limitNum, 10);
|
||||
limitFrom = Math.max(0, limitFrom);
|
||||
limitNum = Math.max(0, limitNum);
|
||||
limitNum = Math.max(0, limitNum);
|
||||
|
||||
return [limitFrom, limitNum];
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the database. Only needed if it was closed before, a database is automatically opened when created.
|
||||
*
|
||||
* @return {Promise<void>} Promise resolved when open.
|
||||
*/
|
||||
open() {
|
||||
open(): Promise<any> {
|
||||
return this.ready().then(() => {
|
||||
return this.db.open();
|
||||
});
|
||||
|
@ -720,7 +740,7 @@ export class SQLiteDB {
|
|||
*
|
||||
* @return {Promise<void>} Promise resolved when ready.
|
||||
*/
|
||||
ready() : Promise<void> {
|
||||
ready(): Promise<void> {
|
||||
return this.promise;
|
||||
}
|
||||
|
||||
|
@ -731,7 +751,7 @@ export class SQLiteDB {
|
|||
* @param {object} [conditions] The conditions to build the where clause. Must not contain numeric indexes.
|
||||
* @return {Promise<void>} Promise resolved if exists, rejected otherwise.
|
||||
*/
|
||||
recordExists(table: string, conditions?: object) : Promise<void> {
|
||||
recordExists(table: string, conditions?: object): Promise<void> {
|
||||
return this.getRecord(table, conditions).then((record) => {
|
||||
if (!record) {
|
||||
return Promise.reject(null);
|
||||
|
@ -747,7 +767,7 @@ export class SQLiteDB {
|
|||
* @param {any[]} [params] An array of sql parameters.
|
||||
* @return {Promise<any>} Promise resolved if exists, rejected otherwise.
|
||||
*/
|
||||
recordExistsSelect(table: string, select='', params=[]) : Promise<any> {
|
||||
recordExistsSelect(table: string, select: string = '', params: any[] = []): Promise<any> {
|
||||
return this.getRecordSelect(table, select, params).then((record) => {
|
||||
if (!record) {
|
||||
return Promise.reject(null);
|
||||
|
@ -762,7 +782,7 @@ export class SQLiteDB {
|
|||
* @param {any[]} [params] An array of sql parameters.
|
||||
* @return {Promise<any>} Promise resolved if exists, rejected otherwise.
|
||||
*/
|
||||
recordExistsSql(sql: string, params?: any[]) : Promise<any> {
|
||||
recordExistsSql(sql: string, params?: any[]): Promise<any> {
|
||||
return this.getRecordSql(sql, params).then((record) => {
|
||||
if (!record) {
|
||||
return Promise.reject(null);
|
||||
|
@ -778,27 +798,27 @@ export class SQLiteDB {
|
|||
* @param {any} [conditions] The conditions to build the where clause. Must not contain numeric indexes.
|
||||
* @return {Promise<any>} Promise resolved when updated.
|
||||
*/
|
||||
updateRecords(table: string, data: any, conditions?: any) : Promise<any> {
|
||||
updateRecords(table: string, data: any, conditions?: any): Promise<any> {
|
||||
|
||||
if (!data || !Object.keys(data).length) {
|
||||
if (!data || !Object.keys(data).length) {
|
||||
// No fields to update, consider it's done.
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
let whereAndParams = this.whereClause(conditions),
|
||||
sets = [],
|
||||
sql,
|
||||
const whereAndParams = this.whereClause(conditions),
|
||||
sets = [];
|
||||
let sql,
|
||||
params;
|
||||
|
||||
this.formatDataToInsert(data);
|
||||
|
||||
for (let key in data) {
|
||||
for (const key in data) {
|
||||
sets.push(`${key} = ?`);
|
||||
}
|
||||
|
||||
sql = `UPDATE ${table} SET ${sets.join(', ')} WHERE ${whereAndParams[0]}`;
|
||||
// Create the list of params using the "data" object and the params for the where clause.
|
||||
params = Object.keys(data).map(key => data[key]).concat(whereAndParams[1]);
|
||||
params = Object.keys(data).map((key) => data[key]).concat(whereAndParams[1]);
|
||||
|
||||
return this.execute(sql, params);
|
||||
}
|
||||
|
@ -812,17 +832,17 @@ export class SQLiteDB {
|
|||
* @param {any[]} [whereParams] Params for the where clause.
|
||||
* @return {Promise<any>} Promise resolved when updated.
|
||||
*/
|
||||
updateRecordsWhere(table: string, data: any, where?: string, whereParams?: any[]) : Promise<any> {
|
||||
if (!data || !Object.keys(data).length) {
|
||||
updateRecordsWhere(table: string, data: any, where?: string, whereParams?: any[]): Promise<any> {
|
||||
if (!data || !Object.keys(data).length) {
|
||||
// No fields to update, consider it's done.
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
let sets = [],
|
||||
sql,
|
||||
const sets = [];
|
||||
let sql,
|
||||
params;
|
||||
|
||||
for (let key in data) {
|
||||
for (const key in data) {
|
||||
sets.push(`${key} = ?`);
|
||||
}
|
||||
|
||||
|
@ -832,7 +852,7 @@ export class SQLiteDB {
|
|||
}
|
||||
|
||||
// Create the list of params using the "data" object and the params for the where clause.
|
||||
params = Object.keys(data).map(key => data[key]);
|
||||
params = Object.keys(data).map((key) => data[key]);
|
||||
if (where && whereParams) {
|
||||
params = params.concat(whereParams[1]);
|
||||
}
|
||||
|
@ -846,16 +866,16 @@ export class SQLiteDB {
|
|||
* @param {object} [conditions] The conditions to build the where clause. Must not contain numeric indexes.
|
||||
* @return {any[]} An array list containing sql 'where' part and 'params'.
|
||||
*/
|
||||
whereClause(conditions={}) : any[] {
|
||||
whereClause(conditions: any = {}): any[] {
|
||||
if (!conditions || !Object.keys(conditions).length) {
|
||||
return ['', []];
|
||||
}
|
||||
|
||||
let where = [],
|
||||
const where = [],
|
||||
params = [];
|
||||
|
||||
for (let key in conditions) {
|
||||
let value = conditions[key];
|
||||
for (const key in conditions) {
|
||||
const value = conditions[key];
|
||||
|
||||
if (typeof value == 'undefined' || value === null) {
|
||||
where.push(key + ' IS NULL');
|
||||
|
@ -875,13 +895,13 @@ export class SQLiteDB {
|
|||
* @param {any[]} values The values field might take.
|
||||
* @return {any[]} An array containing sql 'where' part and 'params'.
|
||||
*/
|
||||
whereClauseList(field: string, values: any[]) : any[] {
|
||||
whereClauseList(field: string, values: any[]): any[] {
|
||||
if (!values || !values.length) {
|
||||
return ["1 = 2", []]; // Fake condition, won't return rows ever.
|
||||
return ['1 = 2', []]; // Fake condition, won't return rows ever.
|
||||
}
|
||||
|
||||
let params = [],
|
||||
select = '';
|
||||
const params = [];
|
||||
let select = '';
|
||||
|
||||
values.forEach((value) => {
|
||||
if (typeof value == 'boolean') {
|
||||
|
@ -903,7 +923,7 @@ export class SQLiteDB {
|
|||
if (params.length == 1) {
|
||||
select = select + field + ' = ?';
|
||||
} else {
|
||||
let questionMarks = ',?'.repeat(params.length).substr(1);
|
||||
const questionMarks = ',?'.repeat(params.length).substr(1);
|
||||
select = select + field + ' IN (' + questionMarks + ')';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,12 +32,12 @@ import { Component, Input, OnChanges, OnDestroy, Output, EventEmitter, SimpleCha
|
|||
})
|
||||
export class CoreChronoComponent implements OnChanges, OnDestroy {
|
||||
@Input() running: boolean; // Set it to true to start the chrono. Set it to false to stop it.
|
||||
@Input() startTime?: number = 0; // Number of milliseconds to put in the chrono before starting.
|
||||
@Input() startTime? = 0; // Number of milliseconds to put in the chrono before starting.
|
||||
@Input() endTime?: number; // Number of milliseconds to stop the chrono.
|
||||
@Input() reset?: boolean; // Set it to true to reset the chrono.
|
||||
@Output() onEnd?: EventEmitter<void>; // Will emit an event when the endTime is reached.
|
||||
|
||||
time: number = 0;
|
||||
time = 0;
|
||||
protected interval;
|
||||
|
||||
constructor(private cdr: ChangeDetectorRef) {
|
||||
|
@ -47,14 +47,14 @@ export class CoreChronoComponent implements OnChanges, OnDestroy {
|
|||
/**
|
||||
* Component being initialized.
|
||||
*/
|
||||
ngOnInit() {
|
||||
ngOnInit(): void {
|
||||
this.time = this.startTime || 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Component being initialized.
|
||||
*/
|
||||
ngOnChanges(changes: {[name: string]: SimpleChange}) {
|
||||
ngOnChanges(changes: { [name: string]: SimpleChange }): void {
|
||||
if (changes && changes.running) {
|
||||
if (changes.running.currentValue) {
|
||||
this.start();
|
||||
|
@ -70,7 +70,7 @@ export class CoreChronoComponent implements OnChanges, OnDestroy {
|
|||
/**
|
||||
* Reset the chrono, stopping it and setting it to startTime.
|
||||
*/
|
||||
protected resetChrono() : void {
|
||||
protected resetChrono(): void {
|
||||
this.stop();
|
||||
this.time = this.startTime || 0;
|
||||
}
|
||||
|
@ -78,7 +78,7 @@ export class CoreChronoComponent implements OnChanges, OnDestroy {
|
|||
/**
|
||||
* Start the chrono if it isn't running.
|
||||
*/
|
||||
protected start() : void {
|
||||
protected start(): void {
|
||||
if (this.interval) {
|
||||
// Already setup.
|
||||
return;
|
||||
|
@ -105,12 +105,12 @@ export class CoreChronoComponent implements OnChanges, OnDestroy {
|
|||
/**
|
||||
* Stop the chrono, leaving the same time it has.
|
||||
*/
|
||||
protected stop() : void {
|
||||
protected stop(): void {
|
||||
clearInterval(this.interval);
|
||||
delete this.interval;
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
ngOnDestroy(): void {
|
||||
this.stop();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@ import { CoreLocalFileComponent } from './local-file/local-file';
|
|||
import { CoreSitePickerComponent } from './site-picker/site-picker';
|
||||
import { CoreTabsComponent } from './tabs/tabs';
|
||||
import { CoreTabComponent } from './tabs/tab';
|
||||
import { CoreRichTextEditorComponent } from './rich-text-editor/rich-text-editor';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
|
@ -57,7 +58,8 @@ import { CoreTabComponent } from './tabs/tab';
|
|||
CoreLocalFileComponent,
|
||||
CoreSitePickerComponent,
|
||||
CoreTabsComponent,
|
||||
CoreTabComponent
|
||||
CoreTabComponent,
|
||||
CoreRichTextEditorComponent
|
||||
],
|
||||
entryComponents: [
|
||||
CoreContextMenuPopoverComponent,
|
||||
|
@ -86,7 +88,8 @@ import { CoreTabComponent } from './tabs/tab';
|
|||
CoreLocalFileComponent,
|
||||
CoreSitePickerComponent,
|
||||
CoreTabsComponent,
|
||||
CoreTabComponent
|
||||
CoreTabComponent,
|
||||
CoreRichTextEditorComponent
|
||||
]
|
||||
})
|
||||
export class CoreComponentsModule {}
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
import { Component, Input, Output, OnInit, OnDestroy, EventEmitter, OnChanges, SimpleChange } from '@angular/core';
|
||||
import { CoreContextMenuComponent } from './context-menu';
|
||||
|
||||
|
||||
/**
|
||||
* This directive adds a item to the Context Menu popover.
|
||||
*
|
||||
|
@ -36,15 +35,16 @@ import { CoreContextMenuComponent } from './context-menu';
|
|||
export class CoreContextMenuItemComponent implements OnInit, OnDestroy, OnChanges {
|
||||
@Input() content: string; // Content of the item.
|
||||
@Input() iconDescription?: string; // Name of the icon to be shown on the left side of the item.
|
||||
@Input() iconAction?: string; // Name of the icon to be shown on the right side of the item. It represents the action to do on
|
||||
// click. If is "spinner" an spinner will be shown. If no icon or spinner is selected, no action
|
||||
// or link will work. If href but no iconAction is provided arrow-right will be used.
|
||||
@Input() iconAction?: string; // Name of the icon to show on the right side of the item. Represents the action to do on click.
|
||||
// If is "spinner" an spinner will be shown.
|
||||
// If no icon or spinner is selected, no action or link will work.
|
||||
// If href but no iconAction is provided arrow-right will be used.
|
||||
@Input() ariaDescription?: string; // Aria label to add to iconDescription.
|
||||
@Input() ariaAction?: string; // Aria label to add to iconAction. If not set, it will be equal to content.
|
||||
@Input() href?: string; // Link to go if no action provided.
|
||||
@Input() captureLink?: boolean|string; // Whether the link needs to be captured by the app.
|
||||
@Input() captureLink?: boolean | string; // Whether the link needs to be captured by the app.
|
||||
@Input() autoLogin?: string; // Whether the link needs to be opened using auto-login.
|
||||
@Input() closeOnClick?: boolean|string = true; // Whether to close the popover when the item is clicked.
|
||||
@Input() closeOnClick?: boolean | string = true; // Whether to close the popover when the item is clicked.
|
||||
@Input() priority?: number; // Used to sort items. The highest priority, the highest position.
|
||||
@Input() badge?: string; // A badge to show in the item.
|
||||
@Input() badgeClass?: number; // A class to set in the badge.
|
||||
|
@ -61,7 +61,7 @@ export class CoreContextMenuItemComponent implements OnInit, OnDestroy, OnChange
|
|||
/**
|
||||
* Component being initialized.
|
||||
*/
|
||||
ngOnInit() {
|
||||
ngOnInit(): void {
|
||||
// Initialize values.
|
||||
this.priority = this.priority || 1;
|
||||
this.closeOnClick = this.getBooleanValue(this.closeOnClick, true);
|
||||
|
@ -88,17 +88,18 @@ export class CoreContextMenuItemComponent implements OnInit, OnDestroy, OnChange
|
|||
* @param {boolean} defaultValue Value to use if undefined.
|
||||
* @return {boolean} Boolean value.
|
||||
*/
|
||||
protected getBooleanValue(value: any, defaultValue: boolean) : boolean {
|
||||
protected getBooleanValue(value: any, defaultValue: boolean): boolean {
|
||||
if (typeof value == 'undefined') {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
return value && value !== 'false';
|
||||
}
|
||||
|
||||
/**
|
||||
* Component destroyed.
|
||||
*/
|
||||
ngOnDestroy() {
|
||||
ngOnDestroy(): void {
|
||||
this.destroyed = true;
|
||||
this.ctxtMenu.removeItem(this);
|
||||
}
|
||||
|
@ -106,7 +107,7 @@ export class CoreContextMenuItemComponent implements OnInit, OnDestroy, OnChange
|
|||
/**
|
||||
* Detect changes on input properties.
|
||||
*/
|
||||
ngOnChanges(changes: {[name: string]: SimpleChange}) {
|
||||
ngOnChanges(changes: { [name: string]: SimpleChange }): void {
|
||||
if (changes.hidden && !changes.hidden.firstChange) {
|
||||
this.ctxtMenu.itemsChanged();
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ export class CoreContextMenuPopoverComponent {
|
|||
/**
|
||||
* Close the popover.
|
||||
*/
|
||||
closeMenu() : void {
|
||||
closeMenu(): void {
|
||||
this.viewCtrl.dismiss();
|
||||
}
|
||||
|
||||
|
@ -49,13 +49,14 @@ export class CoreContextMenuPopoverComponent {
|
|||
* @param {CoreContextMenuItemComponent} item Item clicked.
|
||||
* @return {boolean} Return true if success, false if error.
|
||||
*/
|
||||
itemClicked(event: Event, item: CoreContextMenuItemComponent) : boolean {
|
||||
itemClicked(event: Event, item: CoreContextMenuItemComponent): boolean {
|
||||
if (item.action.observers.length > 0) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
if (!item.iconAction) {
|
||||
this.logger.warn('Items with action must have an icon action to work', item);
|
||||
|
||||
return false;
|
||||
} else if (item.iconAction == 'spinner') {
|
||||
return false;
|
||||
|
|
|
@ -43,15 +43,15 @@ export class CoreContextMenuComponent implements OnInit {
|
|||
this.hideMenu = !this.items.some((item) => {
|
||||
return !item.hidden;
|
||||
});
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Component being initialized.
|
||||
*/
|
||||
ngOnInit() {
|
||||
this.icon = this.icon || 'more';
|
||||
this.ariaLabel = this.title || this.translate.instant('core.info');
|
||||
ngOnInit(): void {
|
||||
this.icon = this.icon || 'more';
|
||||
this.ariaLabel = this.title || this.translate.instant('core.info');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -59,7 +59,7 @@ export class CoreContextMenuComponent implements OnInit {
|
|||
*
|
||||
* @param {CoreContextMenuItemComponent} item The item to add.
|
||||
*/
|
||||
addItem(item: CoreContextMenuItemComponent) : void {
|
||||
addItem(item: CoreContextMenuItemComponent): void {
|
||||
this.items.push(item);
|
||||
this.itemsChanged();
|
||||
}
|
||||
|
@ -67,7 +67,7 @@ export class CoreContextMenuComponent implements OnInit {
|
|||
/**
|
||||
* Function called when the items change.
|
||||
*/
|
||||
itemsChanged() {
|
||||
itemsChanged(): void {
|
||||
this.itemsChangedStream.next();
|
||||
}
|
||||
|
||||
|
@ -76,8 +76,8 @@ export class CoreContextMenuComponent implements OnInit {
|
|||
*
|
||||
* @param {CoreContextMenuItemComponent} item The item to remove.
|
||||
*/
|
||||
removeItem(item: CoreContextMenuItemComponent) : void {
|
||||
let index = this.items.indexOf(item);
|
||||
removeItem(item: CoreContextMenuItemComponent): void {
|
||||
const index = this.items.indexOf(item);
|
||||
if (index >= 0) {
|
||||
this.items.splice(index, 1);
|
||||
}
|
||||
|
@ -89,8 +89,8 @@ export class CoreContextMenuComponent implements OnInit {
|
|||
*
|
||||
* @param {MouseEvent} event Event.
|
||||
*/
|
||||
showContextMenu(event: MouseEvent) : void {
|
||||
let popover = this.popoverCtrl.create(CoreContextMenuPopoverComponent, {title: this.title, items: this.items});
|
||||
showContextMenu(event: MouseEvent): void {
|
||||
const popover = this.popoverCtrl.create(CoreContextMenuPopoverComponent, { title: this.title, items: this.items });
|
||||
popover.present({
|
||||
ev: event
|
||||
});
|
||||
|
|
|
@ -26,7 +26,7 @@ export class CoreCoursePickerMenuPopoverComponent {
|
|||
courses: any[];
|
||||
courseId = -1;
|
||||
|
||||
constructor(private navParams: NavParams, private viewCtrl: ViewController) {
|
||||
constructor(navParams: NavParams, private viewCtrl: ViewController) {
|
||||
this.courses = navParams.get('courses') || [];
|
||||
this.courseId = navParams.get('courseId') || -1;
|
||||
}
|
||||
|
@ -38,8 +38,9 @@ export class CoreCoursePickerMenuPopoverComponent {
|
|||
* @param {any} course Course object clicked.
|
||||
* @return {boolean} Return true if success, false if error.
|
||||
*/
|
||||
coursePicked(event: Event, course: any) : boolean {
|
||||
coursePicked(event: Event, course: any): boolean {
|
||||
this.viewCtrl.dismiss(course);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,5 +29,7 @@ export class CoreEmptyBoxComponent {
|
|||
@Input() icon?: string; // Name of the icon to use.
|
||||
@Input() image?: string; // Image source. If an icon is provided, image won't be used.
|
||||
|
||||
constructor() {}
|
||||
constructor() {
|
||||
// Nothing to do.
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,12 +35,12 @@ import { CoreConstants } from '../../core/constants';
|
|||
export class CoreFileComponent implements OnInit, OnDestroy {
|
||||
@Input() file: any; // The file. Must have a property 'filename' and a 'fileurl' or 'url'
|
||||
@Input() component?: string; // Component the file belongs to.
|
||||
@Input() componentId?: string|number; // Component ID.
|
||||
@Input() componentId?: string | number; // Component ID.
|
||||
@Input() timemodified?: number; // If set, the value will be used to check if the file is outdated.
|
||||
@Input() canDelete?: boolean|string; // Whether file can be deleted.
|
||||
@Input() alwaysDownload?: boolean|string; // Whether it should always display the refresh button when the file is downloaded.
|
||||
// Use it for files that you cannot determine if they're outdated or not.
|
||||
@Input() canDownload?: boolean|string = true; // Whether file can be downloaded.
|
||||
@Input() canDelete?: boolean | string; // Whether file can be deleted.
|
||||
@Input() alwaysDownload?: boolean | string; // Whether it should always display the refresh button when the file is downloaded.
|
||||
// Use it for files that you cannot determine if they're outdated or not.
|
||||
@Input() canDownload?: boolean | string = true; // Whether file can be downloaded.
|
||||
@Output() onDelete?: EventEmitter<string>; // Will notify when the delete button is clicked.
|
||||
|
||||
isDownloaded: boolean;
|
||||
|
@ -64,7 +64,7 @@ export class CoreFileComponent implements OnInit, OnDestroy {
|
|||
/**
|
||||
* Component being initialized.
|
||||
*/
|
||||
ngOnInit() {
|
||||
ngOnInit(): void {
|
||||
this.canDelete = this.utils.isTrueOrOne(this.canDelete);
|
||||
this.alwaysDownload = this.utils.isTrueOrOne(this.alwaysDownload);
|
||||
this.canDownload = this.utils.isTrueOrOne(this.canDownload);
|
||||
|
@ -98,14 +98,14 @@ export class CoreFileComponent implements OnInit, OnDestroy {
|
|||
*
|
||||
* @return {Promise<void>} Promise resolved when state has been calculated.
|
||||
*/
|
||||
protected calculateState() : Promise<void> {
|
||||
protected calculateState(): Promise<void> {
|
||||
return this.filepoolProvider.getFileStateByUrl(this.siteId, this.fileUrl, this.timemodified).then((state) => {
|
||||
let canDownload = this.sitesProvider.getCurrentSite().canDownloadFiles();
|
||||
const canDownload = this.sitesProvider.getCurrentSite().canDownloadFiles();
|
||||
|
||||
this.isDownloaded = state === CoreConstants.DOWNLOADED || state === CoreConstants.OUTDATED;
|
||||
this.isDownloaded = state === CoreConstants.DOWNLOADED || state === CoreConstants.OUTDATED;
|
||||
this.isDownloading = canDownload && state === CoreConstants.DOWNLOADING;
|
||||
this.showDownload = canDownload && (state === CoreConstants.NOT_DOWNLOADED || state === CoreConstants.OUTDATED ||
|
||||
(this.alwaysDownload && state === CoreConstants.DOWNLOADED));
|
||||
this.showDownload = canDownload && (state === CoreConstants.NOT_DOWNLOADED || state === CoreConstants.OUTDATED ||
|
||||
(this.alwaysDownload && state === CoreConstants.DOWNLOADED));
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -114,25 +114,27 @@ export class CoreFileComponent implements OnInit, OnDestroy {
|
|||
*
|
||||
* @return {Promise<string>} Promise resolved when file is downloaded.
|
||||
*/
|
||||
protected downloadFile() : Promise<string> {
|
||||
protected downloadFile(): Promise<string> {
|
||||
if (!this.sitesProvider.getCurrentSite().canDownloadFiles()) {
|
||||
this.domUtils.showErrorModal('core.cannotdownloadfiles', true);
|
||||
|
||||
return Promise.reject(null);
|
||||
}
|
||||
|
||||
this.isDownloading = true;
|
||||
return this.filepoolProvider.downloadUrl(this.siteId, this.fileUrl, false, this.component, this.componentId,
|
||||
this.timemodified, undefined, undefined, this.file).catch(() => {
|
||||
|
||||
// Call calculateState to make sure we have the right state.
|
||||
return this.calculateState().then(() => {
|
||||
if (this.isDownloaded) {
|
||||
return this.filepoolProvider.getInternalUrlByUrl(this.siteId, this.fileUrl);
|
||||
} else {
|
||||
return Promise.reject(null);
|
||||
}
|
||||
return this.filepoolProvider.downloadUrl(this.siteId, this.fileUrl, false, this.component, this.componentId,
|
||||
this.timemodified, undefined, undefined, this.file).catch(() => {
|
||||
|
||||
// Call calculateState to make sure we have the right state.
|
||||
return this.calculateState().then(() => {
|
||||
if (this.isDownloaded) {
|
||||
return this.filepoolProvider.getInternalUrlByUrl(this.siteId, this.fileUrl);
|
||||
} else {
|
||||
return Promise.reject(null);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -140,33 +142,35 @@ export class CoreFileComponent implements OnInit, OnDestroy {
|
|||
*
|
||||
* @return {Promise<string>} Promise resolved when file is opened.
|
||||
*/
|
||||
protected openFile() : Promise<any> {
|
||||
let fixedUrl = this.sitesProvider.getCurrentSite().fixPluginfileURL(this.fileUrl),
|
||||
promise;
|
||||
protected openFile(): Promise<any> {
|
||||
const fixedUrl = this.sitesProvider.getCurrentSite().fixPluginfileURL(this.fileUrl);
|
||||
let promise;
|
||||
|
||||
if (this.fileProvider.isAvailable()) {
|
||||
promise = Promise.resolve().then(() => {
|
||||
// The file system is available.
|
||||
let isWifi = !this.appProvider.isNetworkAccessLimited(),
|
||||
const isWifi = !this.appProvider.isNetworkAccessLimited(),
|
||||
isOnline = this.appProvider.isOnline();
|
||||
|
||||
if (this.isDownloaded && !this.showDownload) {
|
||||
// File is downloaded, get the local file URL.
|
||||
return this.filepoolProvider.getUrlByUrl(this.siteId, this.fileUrl,
|
||||
this.component, this.componentId, this.timemodified, false, false, this.file);
|
||||
this.component, this.componentId, this.timemodified, false, false, this.file);
|
||||
} else {
|
||||
if (!isOnline && !this.isDownloaded) {
|
||||
// Not downloaded and user is offline, reject.
|
||||
return Promise.reject(this.translate.instant('core.networkerrormsg'));
|
||||
}
|
||||
|
||||
let isDownloading = this.isDownloading;
|
||||
const isDownloading = this.isDownloading;
|
||||
this.isDownloading = true; // This check could take a while, show spinner.
|
||||
|
||||
return this.filepoolProvider.shouldDownloadBeforeOpen(fixedUrl, this.fileSize).then(() => {
|
||||
if (isDownloading) {
|
||||
// It's already downloading, stop.
|
||||
return;
|
||||
}
|
||||
|
||||
// Download and then return the local URL.
|
||||
return this.downloadFile();
|
||||
}, () => {
|
||||
|
@ -181,7 +185,7 @@ export class CoreFileComponent implements OnInit, OnDestroy {
|
|||
} else {
|
||||
// Outdated but offline, so we return the local URL.
|
||||
return this.filepoolProvider.getUrlByUrl(this.siteId, this.fileUrl,
|
||||
this.component, this.componentId, this.timemodified, false, false, this.file);
|
||||
this.component, this.componentId, this.timemodified, false, false, this.file);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -231,7 +235,7 @@ export class CoreFileComponent implements OnInit, OnDestroy {
|
|||
* @param {Event} e Click event.
|
||||
* @param {boolean} openAfterDownload Whether the file should be opened after download.
|
||||
*/
|
||||
download(e: Event, openAfterDownload: boolean) : void {
|
||||
download(e: Event, openAfterDownload: boolean): void {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
|
@ -243,6 +247,7 @@ export class CoreFileComponent implements OnInit, OnDestroy {
|
|||
|
||||
if (!this.appProvider.isOnline() && (!openAfterDownload || (openAfterDownload && !this.isDownloaded))) {
|
||||
this.domUtils.showErrorModal('core.networkerrormsg', true);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -253,27 +258,27 @@ export class CoreFileComponent implements OnInit, OnDestroy {
|
|||
});
|
||||
} else {
|
||||
// File doesn't need to be opened (it's a prefetch). Show confirm modal if file size is defined and it's big.
|
||||
promise = this.fileSize ? this.domUtils.confirmDownloadSize({size: this.fileSize, total: true}) : Promise.resolve();
|
||||
promise = this.fileSize ? this.domUtils.confirmDownloadSize({ size: this.fileSize, total: true }) : Promise.resolve();
|
||||
promise.then(() => {
|
||||
// User confirmed, add the file to queue.
|
||||
this.filepoolProvider.invalidateFileByUrl(this.siteId, this.fileUrl).finally(() => {
|
||||
this.isDownloading = true;
|
||||
this.filepoolProvider.addToQueueByUrl(this.siteId, this.fileUrl, this.component,
|
||||
this.componentId, this.timemodified, undefined, undefined, 0, this.file).catch((error) => {
|
||||
this.domUtils.showErrorModalDefault(error, 'core.errordownloading', true);
|
||||
this.calculateState();
|
||||
});
|
||||
this.componentId, this.timemodified, undefined, undefined, 0, this.file).catch((error) => {
|
||||
this.domUtils.showErrorModalDefault(error, 'core.errordownloading', true);
|
||||
this.calculateState();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the file.
|
||||
*
|
||||
* @param {Event} e Click event.
|
||||
*/
|
||||
deleteFile(e: Event) : void {
|
||||
deleteFile(e: Event): void {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
|
@ -285,7 +290,7 @@ export class CoreFileComponent implements OnInit, OnDestroy {
|
|||
/**
|
||||
* Component destroyed.
|
||||
*/
|
||||
ngOnDestroy() {
|
||||
ngOnDestroy(): void {
|
||||
this.observer && this.observer.off();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,8 +51,8 @@ export class CoreIframeComponent implements OnInit {
|
|||
/**
|
||||
* Component being initialized.
|
||||
*/
|
||||
ngOnInit() {
|
||||
let iframe: HTMLIFrameElement = this.iframe && this.iframe.nativeElement;
|
||||
ngOnInit(): void {
|
||||
const iframe: HTMLIFrameElement = this.iframe && this.iframe.nativeElement;
|
||||
|
||||
this.safeUrl = this.sanitizer.bypassSecurityTrustResourceUrl(this.src);
|
||||
this.iframeWidth = this.domUtils.formatPixelsSize(this.iframeWidth) || '100%';
|
||||
|
@ -85,7 +85,7 @@ export class CoreIframeComponent implements OnInit {
|
|||
* @param {any} element Element to treat.
|
||||
* @return {{ window: Window, document: Document }} Window and Document.
|
||||
*/
|
||||
protected getContentWindowAndDocument(element: any) : { window: Window, document: Document } {
|
||||
protected getContentWindowAndDocument(element: any): { window: Window, document: Document } {
|
||||
let contentWindow: Window = element.contentWindow,
|
||||
contentDocument: Document = element.contentDocument || (contentWindow && contentWindow.document);
|
||||
|
||||
|
@ -106,7 +106,7 @@ export class CoreIframeComponent implements OnInit {
|
|||
}
|
||||
}
|
||||
|
||||
return {window: contentWindow, document: contentDocument};
|
||||
return { window: contentWindow, document: contentDocument };
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -115,7 +115,7 @@ export class CoreIframeComponent implements OnInit {
|
|||
*
|
||||
* @param {any} element Element to treat.
|
||||
*/
|
||||
protected treatFrame(element: any) : void {
|
||||
protected treatFrame(element: any): void {
|
||||
if (element) {
|
||||
let winAndDoc = this.getContentWindowAndDocument(element);
|
||||
// Redefine window.open in this element and sub frames, it might have been loaded already.
|
||||
|
@ -139,10 +139,10 @@ export class CoreIframeComponent implements OnInit {
|
|||
* @param {Window} contentWindow The window of the element contents.
|
||||
* @param {Document} contentDocument The document of the element contents.
|
||||
*/
|
||||
protected redefineWindowOpen(element: any, contentWindow: Window, contentDocument: Document) : void {
|
||||
protected redefineWindowOpen(element: any, contentWindow: Window, contentDocument: Document): void {
|
||||
if (contentWindow) {
|
||||
// Intercept window.open.
|
||||
contentWindow.open = (url: string) : Window => {
|
||||
contentWindow.open = (url: string): Window => {
|
||||
const scheme = this.urlUtils.getUrlScheme(url);
|
||||
if (!scheme) {
|
||||
// It's a relative URL, use the frame src to create the full URL.
|
||||
|
@ -153,10 +153,12 @@ export class CoreIframeComponent implements OnInit {
|
|||
url = this.textUtils.concatenatePaths(dirAndFile.directory, url);
|
||||
} else {
|
||||
this.logger.warn('Cannot get iframe dir path to open relative url', url, element);
|
||||
|
||||
return new Window(); // Return new Window object.
|
||||
}
|
||||
} else {
|
||||
this.logger.warn('Cannot get iframe src to open relative url', url, element);
|
||||
|
||||
return new Window(); // Return new Window object.
|
||||
}
|
||||
}
|
||||
|
@ -198,7 +200,7 @@ export class CoreIframeComponent implements OnInit {
|
|||
* @param {any} element Element to treat.
|
||||
* @param {Document} contentDocument The document of the element contents.
|
||||
*/
|
||||
protected treatLinks(element: any, contentDocument: Document) : void {
|
||||
protected treatLinks(element: any, contentDocument: Document): void {
|
||||
if (!contentDocument) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -47,12 +47,12 @@ export class CoreInputErrorsComponent implements OnInit {
|
|||
@Input() errorMessages?: any;
|
||||
errorKeys: any[];
|
||||
|
||||
constructor(private translate: TranslateService) {}
|
||||
constructor(private translate: TranslateService) { }
|
||||
|
||||
/**
|
||||
* Component is being initialized.
|
||||
*/
|
||||
ngOnInit() {
|
||||
ngOnInit(): void {
|
||||
this.initErrorMessages();
|
||||
|
||||
this.errorKeys = Object.keys(this.errorMessages);
|
||||
|
@ -61,11 +61,11 @@ export class CoreInputErrorsComponent implements OnInit {
|
|||
/**
|
||||
* Initialize some common errors if they aren't set.
|
||||
*/
|
||||
protected initErrorMessages() {
|
||||
this.errorMessages = this.errorMessages || {};
|
||||
protected initErrorMessages(): void {
|
||||
this.errorMessages = this.errorMessages || {};
|
||||
|
||||
this.errorMessages.required = this.errorMessages.required || this.translate.instant('core.required');
|
||||
this.errorMessages.email = this.errorMessages.email || this.translate.instant('core.login.invalidemail');
|
||||
this.errorMessages.required = this.errorMessages.required || this.translate.instant('core.required');
|
||||
this.errorMessages.email = this.errorMessages.email || this.translate.instant('core.login.invalidemail');
|
||||
this.errorMessages.date = this.errorMessages.date || this.translate.instant('core.login.invaliddate');
|
||||
this.errorMessages.datetime = this.errorMessages.datetime || this.translate.instant('core.login.invaliddate');
|
||||
this.errorMessages.datetimelocal = this.errorMessages.datetimelocal || this.translate.instant('core.login.invaliddate');
|
||||
|
@ -73,24 +73,6 @@ export class CoreInputErrorsComponent implements OnInit {
|
|||
this.errorMessages.url = this.errorMessages.url || this.translate.instant('core.login.invalidurl');
|
||||
|
||||
// @todo: Check how to handle min/max errors once we have a test case to use. Also, review previous errors.
|
||||
// ['min', 'max'].forEach((type) => {
|
||||
// // Initialize min/max errors if needed.
|
||||
// if (!this.errorMessages[type]) {
|
||||
// if (input && typeof input[type] != 'undefined' && input[type] !== '') {
|
||||
// var value = input[type];
|
||||
// if (input.type == 'date' || input.type == 'datetime' || input.type == 'datetime-local') {
|
||||
// var date = moment(value);
|
||||
// if (date.isValid()) {
|
||||
// value = moment(value).format($translate.instant('core.dfdaymonthyear'));
|
||||
// }
|
||||
// }
|
||||
|
||||
// scope.errorMessages[type] = $translate.instant('core.login.invalidvalue' + type, {$a: value});
|
||||
// } else {
|
||||
// scope.errorMessages[type] = $translate.instant('core.login.profileinvaliddata');
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -43,12 +43,12 @@ export class CoreLoadingComponent implements OnInit {
|
|||
@Input() hideUntil: boolean; // Determine when should the contents be shown.
|
||||
@Input() message?: string; // Message to show while loading.
|
||||
|
||||
constructor(private translate: TranslateService) {}
|
||||
constructor(private translate: TranslateService) { }
|
||||
|
||||
/**
|
||||
* Component being initialized.
|
||||
*/
|
||||
ngOnInit() {
|
||||
ngOnInit(): void {
|
||||
if (!this.message) {
|
||||
// Default loading message.
|
||||
this.message = this.translate.instant('core.loading');
|
||||
|
|
|
@ -33,8 +33,8 @@ import * as moment from 'moment';
|
|||
})
|
||||
export class CoreLocalFileComponent implements OnInit {
|
||||
@Input() file: any; // A fileEntry retrieved using CoreFileProvider.getFile or similar.
|
||||
@Input() manage?: boolean|string; // Whether the user can manage the file (edit and delete).
|
||||
@Input() overrideClick?: boolean|string; // Whether the default item click should be overridden.
|
||||
@Input() manage?: boolean | string; // Whether the user can manage the file (edit and delete).
|
||||
@Input() overrideClick?: boolean | string; // Whether the default item click should be overridden.
|
||||
@Output() onDelete?: EventEmitter<void>; // Will notify when the file is deleted.
|
||||
@Output() onRename?: EventEmitter<any>; // Will notify when the file is renamed. Receives the FileEntry as the param.
|
||||
@Output() onClick?: EventEmitter<void>; // Will notify when the file is clicked. Only if overrideClick is true.
|
||||
|
@ -44,7 +44,7 @@ export class CoreLocalFileComponent implements OnInit {
|
|||
fileExtension: string;
|
||||
size: string;
|
||||
timemodified: string;
|
||||
newFileName: string = '';
|
||||
newFileName = '';
|
||||
editMode: boolean;
|
||||
relativePath: string;
|
||||
|
||||
|
@ -59,7 +59,7 @@ export class CoreLocalFileComponent implements OnInit {
|
|||
/**
|
||||
* Component being initialized.
|
||||
*/
|
||||
ngOnInit() {
|
||||
ngOnInit(): void {
|
||||
this.manage = this.utils.isTrueOrOne(this.manage);
|
||||
|
||||
// Let's calculate the relative path for the file.
|
||||
|
@ -83,11 +83,8 @@ export class CoreLocalFileComponent implements OnInit {
|
|||
|
||||
/**
|
||||
* Load the basic data for the file.
|
||||
*
|
||||
* @param {[type]} scope [description]
|
||||
* @param {[type]} file [description]
|
||||
*/
|
||||
protected loadFileBasicData() {
|
||||
protected loadFileBasicData(): void {
|
||||
this.fileName = this.file.name;
|
||||
this.fileIcon = this.mimeUtils.getFileIcon(this.file.name);
|
||||
this.fileExtension = this.mimeUtils.getFileExtension(this.file.name);
|
||||
|
@ -98,7 +95,7 @@ export class CoreLocalFileComponent implements OnInit {
|
|||
*
|
||||
* @param {Event} e Click event.
|
||||
*/
|
||||
fileClicked(e: Event) : void {
|
||||
fileClicked(e: Event): void {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
|
@ -107,14 +104,14 @@ export class CoreLocalFileComponent implements OnInit {
|
|||
} else {
|
||||
this.utils.openFile(this.file.toURL());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Activate the edit mode.
|
||||
*
|
||||
* @param {Event} e Click event.
|
||||
*/
|
||||
activateEdit(e: Event) : void {
|
||||
activateEdit(e: Event): void {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.editMode = true;
|
||||
|
@ -124,21 +121,22 @@ export class CoreLocalFileComponent implements OnInit {
|
|||
// $timeout(function() {
|
||||
// $mmUtil.focusElement(element[0].querySelector('input'));
|
||||
// });
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Rename the file.
|
||||
*
|
||||
* @param {string} newName New name.
|
||||
*/
|
||||
changeName(newName: string) : void {
|
||||
changeName(newName: string): void {
|
||||
if (newName == this.file.name) {
|
||||
// Name hasn't changed, stop.
|
||||
this.editMode = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let modal = this.domUtils.showModalLoading(),
|
||||
const modal = this.domUtils.showModalLoading(),
|
||||
fileAndDir = this.fileProvider.getFileAndDirectoryFromPath(this.relativePath),
|
||||
newPath = this.textUtils.concatenatePaths(fileAndDir.directory, newName);
|
||||
|
||||
|
@ -152,27 +150,27 @@ export class CoreLocalFileComponent implements OnInit {
|
|||
this.editMode = false;
|
||||
this.file = fileEntry;
|
||||
this.loadFileBasicData();
|
||||
this.onRename.emit({file: this.file});
|
||||
this.onRename.emit({ file: this.file });
|
||||
}).catch(() => {
|
||||
this.domUtils.showErrorModal('core.errorrenamefile', true);
|
||||
});
|
||||
}).finally(() => {
|
||||
modal.dismiss();
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the file.
|
||||
*
|
||||
* @param {Event} e Click event.
|
||||
*/
|
||||
deleteFile(e: Event) : void {
|
||||
deleteFile(e: Event): void {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
// Ask confirmation.
|
||||
this.domUtils.showConfirm(this.translate.instant('core.confirmdeletefile')).then(() => {
|
||||
let modal = this.domUtils.showModalLoading();
|
||||
const modal = this.domUtils.showModalLoading();
|
||||
this.fileProvider.removeFile(this.relativePath).then(() => {
|
||||
this.onDelete.emit();
|
||||
}).catch(() => {
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
*[core-mark-required] {
|
||||
.core-input-required-asterisk, .icon.core-input-required-asterisk {
|
||||
color: $red !important;
|
||||
font-size: 8px;
|
||||
padding-left: 4px;
|
||||
line-height: 100%;
|
||||
vertical-align: top;
|
||||
}
|
||||
.core-input-required-asterisk, .icon.core-input-required-asterisk {
|
||||
color: $red !important;
|
||||
font-size: 8px;
|
||||
padding-left: 4px;
|
||||
line-height: 100%;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ import { CoreUtilsProvider } from '../../providers/utils/utils';
|
|||
templateUrl: 'mark-required.html'
|
||||
})
|
||||
export class CoreMarkRequiredComponent implements OnInit, AfterViewInit {
|
||||
@Input('core-mark-required') coreMarkRequired: boolean|string = true;
|
||||
@Input('core-mark-required') coreMarkRequired: boolean | string = true;
|
||||
protected element: HTMLElement;
|
||||
requiredLabel: string;
|
||||
|
||||
|
@ -45,14 +45,14 @@ export class CoreMarkRequiredComponent implements OnInit, AfterViewInit {
|
|||
/**
|
||||
* Component being initialized.
|
||||
*/
|
||||
ngOnInit() {
|
||||
ngOnInit(): void {
|
||||
this.coreMarkRequired = this.utils.isTrueOrOne(this.coreMarkRequired);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after the view is initialized.
|
||||
*/
|
||||
ngAfterViewInit() : void {
|
||||
ngAfterViewInit(): void {
|
||||
if (this.coreMarkRequired) {
|
||||
// Add the "required" to the aria-label.
|
||||
const ariaLabel = this.element.getAttribute('aria-label') || this.textUtils.cleanTags(this.element.innerHTML, true);
|
||||
|
|
|
@ -27,17 +27,17 @@ import { DomSanitizer, SafeStyle } from '@angular/platform-browser';
|
|||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class CoreProgressBarComponent implements OnChanges {
|
||||
@Input() progress: number|string; // Percentage from 0 to 100.
|
||||
@Input() progress: number | string; // Percentage from 0 to 100.
|
||||
@Input() text?: string; // Percentage in text to be shown at the right. If not defined, progress will be used.
|
||||
width: SafeStyle;
|
||||
protected textSupplied = false;
|
||||
|
||||
constructor(private sanitizer: DomSanitizer) {}
|
||||
constructor(private sanitizer: DomSanitizer) { }
|
||||
|
||||
/**
|
||||
* Detect changes on input properties.
|
||||
*/
|
||||
ngOnChanges(changes: {[name: string]: SimpleChange}) {
|
||||
ngOnChanges(changes: { [name: string]: SimpleChange }): void {
|
||||
if (changes.text && typeof changes.text.currentValue != 'undefined') {
|
||||
// User provided a custom text, don't use default.
|
||||
this.textSupplied = true;
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
<div [hidden]="!rteEnabled">
|
||||
<div #editor contenteditable="true" class="core-rte-editor" tappable [attr.data-placeholder-text]="placeholder">
|
||||
</div>
|
||||
|
||||
<!-- https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand -->
|
||||
<div #decorate class="formatOptions">
|
||||
<button data-command="bold"><strong>B</strong></button>
|
||||
<button data-command="italic"><i>I</i></button>
|
||||
<button data-command="underline"><u>U</u></button>
|
||||
<button data-command="formatBlock|<p>">Normal</button>
|
||||
<button data-command="formatBlock|<h1>">H1</button>
|
||||
<button data-command="formatBlock|<h2>">H2</button>
|
||||
<button data-command="formatBlock|<h3>">H3</button>
|
||||
<button data-command="formatBlock|<pre>">Pre</button>
|
||||
<button data-command="insertOrderedList">OL</button>
|
||||
<button data-command="insertUnorderedList">UL</button>
|
||||
<button data-command="removeFormat">Tx</button>
|
||||
<button (click)="toggleEditor($event)">Toggle Editor</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div [hidden]="rteEnabled">
|
||||
<ion-textarea #textarea class="core-textarea" [placeholder]="placeholder" ngControl="control" (ionChange)="onChange($event)"></ion-textarea>
|
||||
<div class="formatOptions">
|
||||
<button tappable (click)="toggleEditor($event)">Toggle Editor</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
core-rich-text-editor {
|
||||
height: 40vh;
|
||||
overflow: hidden;
|
||||
min-height: 30vh;
|
||||
|
||||
> div {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.core-rte-editor, .core-textarea {
|
||||
padding: 2px;
|
||||
margin: 2px;
|
||||
width: 100%;
|
||||
resize: none;
|
||||
background-color: $white;
|
||||
flex-grow: 1;
|
||||
* {
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.core-rte-editor {
|
||||
-webkit-user-select: auto !important;
|
||||
word-wrap: break-word;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
cursor: text;
|
||||
img {
|
||||
padding-left: 2px;
|
||||
max-width: 95%;
|
||||
}
|
||||
&:empty:before {
|
||||
content: attr(data-placeholder-text);
|
||||
display: block;
|
||||
color: $gray-light;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.core-textarea textarea {
|
||||
margin: 0 !important;
|
||||
padding: 0;
|
||||
height: 100% !important;
|
||||
width: 100% !important;
|
||||
resize: none;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
div.formatOptions {
|
||||
background: $gray-dark;
|
||||
margin: 5px 1px 15px 1px;
|
||||
text-align: center;
|
||||
flex-grow: 0;
|
||||
width: 100%;
|
||||
z-index: 1;
|
||||
button {
|
||||
background: $gray-dark;
|
||||
color: $white;
|
||||
font-size: 1.1em;
|
||||
height: 35px;
|
||||
min-width: 30px;
|
||||
padding-left: 1px;
|
||||
padding-right: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,199 @@
|
|||
// (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, Input, Output, EventEmitter, ViewChild, ElementRef } from '@angular/core';
|
||||
import { TextInput } from 'ionic-angular';
|
||||
import { CoreDomUtilsProvider } from '../../providers/utils/dom';
|
||||
import { FormControl } from '@angular/forms';
|
||||
import { Keyboard } from '@ionic-native/keyboard';
|
||||
|
||||
/**
|
||||
* Directive to display a rich text editor if enabled.
|
||||
*
|
||||
* If enabled, this directive will show a rich text editor. Otherwise it'll show a regular textarea.
|
||||
*
|
||||
* This directive requires an OBJECT model. The text written in the editor or textarea will be stored inside
|
||||
* a "text" property in that object. This is to ensure 2-way data-binding, since using a string as a model
|
||||
* could be easily broken.
|
||||
*
|
||||
* Example:
|
||||
* <core-rich-text-editor item-content [control]="control" [placeholder]="field.name"></core-rich-text-editor>
|
||||
*
|
||||
* In the example above, the text written in the editor will be stored in newpost.text.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'core-rich-text-editor',
|
||||
templateUrl: 'rich-text-editor.html'
|
||||
})
|
||||
export class CoreRichTextEditorComponent {
|
||||
// Based on: https://github.com/judgewest2000/Ionic3RichText/
|
||||
// @todo: Resize, images, anchor button, fullscreen...
|
||||
|
||||
@Input() placeholder? = ''; // Placeholder to set in textarea.
|
||||
@Input() control: FormControl; // Form control.
|
||||
@Output() contentChanged: EventEmitter<string>;
|
||||
|
||||
@ViewChild('editor') editor: ElementRef; // WYSIWYG editor.
|
||||
@ViewChild('textarea') textarea: TextInput; // Textarea editor.
|
||||
@ViewChild('decorate') decorate: ElementRef; // Buttons.
|
||||
|
||||
rteEnabled = false;
|
||||
uniqueId = `rte{Math.floor(Math.random() * 1000000)}`;
|
||||
editorElement: HTMLDivElement;
|
||||
|
||||
constructor(private domUtils: CoreDomUtilsProvider, private keyboard: Keyboard) {
|
||||
this.contentChanged = new EventEmitter<string>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Init editor
|
||||
*/
|
||||
ngAfterContentInit(): void {
|
||||
this.domUtils.isRichTextEditorEnabled().then((enabled) => {
|
||||
this.rteEnabled = !!enabled;
|
||||
});
|
||||
|
||||
// Setup the editor.
|
||||
this.editorElement = this.editor.nativeElement as HTMLDivElement;
|
||||
this.editorElement.innerHTML = this.control.value;
|
||||
this.textarea.value = this.control.value;
|
||||
this.control.setValue(this.control.value);
|
||||
|
||||
this.editorElement.onchange = this.onChange.bind(this);
|
||||
this.editorElement.onkeyup = this.onChange.bind(this);
|
||||
this.editorElement.onpaste = this.onChange.bind(this);
|
||||
this.editorElement.oninput = this.onChange.bind(this);
|
||||
|
||||
// Setup button actions.
|
||||
const buttons = (this.decorate.nativeElement as HTMLDivElement).getElementsByTagName('button');
|
||||
for (let i = 0; i < buttons.length; i++) {
|
||||
const button = buttons[i];
|
||||
let command = button.getAttribute('data-command');
|
||||
|
||||
if (command) {
|
||||
if (command.includes('|')) {
|
||||
const parameter = command.split('|')[1];
|
||||
command = command.split('|')[0];
|
||||
|
||||
button.addEventListener('click', ($event) => {
|
||||
this.buttonAction($event, command, parameter);
|
||||
});
|
||||
} else {
|
||||
button.addEventListener('click', ($event) => {
|
||||
this.buttonAction($event, command);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* On change function to sync with form data.
|
||||
*
|
||||
* @param {Event} $event The event.
|
||||
*/
|
||||
onChange($event: Event): void {
|
||||
if (this.rteEnabled) {
|
||||
if (this.isNullOrWhiteSpace(this.editorElement.innerText)) {
|
||||
this.clearText();
|
||||
} else {
|
||||
this.control.setValue(this.editorElement.innerHTML);
|
||||
}
|
||||
} else {
|
||||
if (this.isNullOrWhiteSpace(this.textarea.value)) {
|
||||
this.clearText();
|
||||
} else {
|
||||
this.control.setValue(this.textarea.value);
|
||||
}
|
||||
}
|
||||
this.contentChanged.emit(this.control.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle from rte editor to textarea syncing values.
|
||||
*
|
||||
* @param {Event} $event The event.
|
||||
*/
|
||||
toggleEditor($event: Event): void {
|
||||
$event.preventDefault();
|
||||
$event.stopPropagation();
|
||||
|
||||
if (this.isNullOrWhiteSpace(this.control.value)) {
|
||||
this.clearText();
|
||||
} else {
|
||||
this.editorElement.innerHTML = this.control.value;
|
||||
this.textarea.value = this.control.value;
|
||||
}
|
||||
|
||||
this.rteEnabled = !this.rteEnabled;
|
||||
|
||||
// Set focus and cursor at the end.
|
||||
setTimeout(() => {
|
||||
if (this.rteEnabled) {
|
||||
this.editorElement.focus();
|
||||
|
||||
const range = document.createRange();
|
||||
range.selectNodeContents(this.editorElement);
|
||||
range.collapse(false);
|
||||
|
||||
const sel = window.getSelection();
|
||||
sel.removeAllRanges();
|
||||
sel.addRange(range);
|
||||
} else {
|
||||
this.textarea.setFocus();
|
||||
}
|
||||
setTimeout(() => {
|
||||
this.keyboard.show();
|
||||
}, 1);
|
||||
}, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if text is empty.
|
||||
* @param {string} value text
|
||||
*/
|
||||
protected isNullOrWhiteSpace(value: string): boolean {
|
||||
if (value == null || typeof value == 'undefined') {
|
||||
return true;
|
||||
}
|
||||
|
||||
value = value.replace(/[\n\r]/g, '');
|
||||
value = value.split(' ').join('');
|
||||
|
||||
return value.length === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the text.
|
||||
*/
|
||||
clearText(): void {
|
||||
this.editorElement.innerHTML = '<p></p>';
|
||||
this.textarea.value = '';
|
||||
this.control.setValue(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute an action over the selected text.
|
||||
* API docs: https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand
|
||||
*
|
||||
* @param {any} $event Event data
|
||||
* @param {string} command Command to execute.
|
||||
* @param {any} [parameters] Parameters of the command.
|
||||
*/
|
||||
protected buttonAction($event: any, command: string, parameters: any = null): void {
|
||||
$event.preventDefault();
|
||||
$event.stopPropagation();
|
||||
document.execCommand(command, false, parameters);
|
||||
}
|
||||
}
|
|
@ -31,20 +31,20 @@ import { CoreUtilsProvider } from '../../providers/utils/utils';
|
|||
templateUrl: 'search-box.html'
|
||||
})
|
||||
export class CoreSearchBoxComponent implements OnInit {
|
||||
@Input() initialValue?: string = ''; // Initial value for search text.
|
||||
@Input() searchLabel?: string ; // Label to be used on action button.
|
||||
@Input() initialValue? = ''; // Initial value for search text.
|
||||
@Input() searchLabel?: string; // Label to be used on action button.
|
||||
@Input() placeholder?: string; // Placeholder text for search text input.
|
||||
@Input() autocorrect?: string = 'on'; // Enables/disable Autocorrection on search text input.
|
||||
@Input() spellcheck?: string|boolean = true; // Enables/disable Spellchecker on search text input.
|
||||
@Input() autoFocus?: string|boolean; // Enables/disable Autofocus when entering view.
|
||||
@Input() lengthCheck?: number = 3; // Check value length before submit. If 0, any string will be submitted.
|
||||
@Input() autocorrect? = 'on'; // Enables/disable Autocorrection on search text input.
|
||||
@Input() spellcheck?: string | boolean = true; // Enables/disable Spellchecker on search text input.
|
||||
@Input() autoFocus?: string | boolean; // Enables/disable Autofocus when entering view.
|
||||
@Input() lengthCheck? = 3; // Check value length before submit. If 0, any string will be submitted.
|
||||
@Output() onSubmit: EventEmitter<string>; // Send data when submitting the search form.
|
||||
|
||||
constructor(private translate: TranslateService, private utils: CoreUtilsProvider) {
|
||||
this.onSubmit = new EventEmitter();
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
ngOnInit(): void {
|
||||
this.searchLabel = this.searchLabel || this.translate.instant('core.search');
|
||||
this.placeholder = this.placeholder || this.translate.instant('core.search');
|
||||
this.spellcheck = this.utils.isTrueOrOne(this.spellcheck);
|
||||
|
@ -55,7 +55,7 @@ export class CoreSearchBoxComponent implements OnInit {
|
|||
*
|
||||
* @param {string} value Entered value.
|
||||
*/
|
||||
submitForm(value: string) {
|
||||
submitForm(value: string): void {
|
||||
if (value.length < this.lengthCheck) {
|
||||
// The view should handle this case, but we check it here too just in case.
|
||||
return;
|
||||
|
@ -63,5 +63,4 @@ export class CoreSearchBoxComponent implements OnInit {
|
|||
|
||||
this.onSubmit.emit(value);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -35,12 +35,12 @@ import { CoreUtilsProvider } from '../../providers/utils/utils';
|
|||
})
|
||||
export class CoreShowPasswordComponent implements OnInit, AfterViewInit {
|
||||
@Input() name: string; // Name of the input affected.
|
||||
@Input() initialShown?: boolean|string; // Whether the password should be shown at start.
|
||||
@Input() initialShown?: boolean | string; // Whether the password should be shown at start.
|
||||
|
||||
shown: boolean; // Whether the password is shown.
|
||||
label: string; // Label for the button to show/hide.
|
||||
iconName: string; // Name of the icon of the button to show/hide.
|
||||
selector: string = ''; // Selector to identify the input.
|
||||
selector = ''; // Selector to identify the input.
|
||||
|
||||
protected input: HTMLInputElement; // Input affected.
|
||||
protected element: HTMLElement; // Current element.
|
||||
|
@ -52,7 +52,7 @@ export class CoreShowPasswordComponent implements OnInit, AfterViewInit {
|
|||
/**
|
||||
* Component being initialized.
|
||||
*/
|
||||
ngOnInit() {
|
||||
ngOnInit(): void {
|
||||
this.shown = this.utils.isTrueOrOne(this.initialShown);
|
||||
this.selector = 'input[name="' + this.name + '"]';
|
||||
this.setData();
|
||||
|
@ -61,14 +61,14 @@ export class CoreShowPasswordComponent implements OnInit, AfterViewInit {
|
|||
/**
|
||||
* View has been initialized.
|
||||
*/
|
||||
ngAfterViewInit() {
|
||||
ngAfterViewInit(): void {
|
||||
this.searchInput();
|
||||
}
|
||||
|
||||
/**
|
||||
* Search the input to show/hide.
|
||||
*/
|
||||
protected searchInput() {
|
||||
protected searchInput(): void {
|
||||
// Search the input.
|
||||
this.input = <HTMLInputElement> this.element.querySelector(this.selector);
|
||||
|
||||
|
@ -89,7 +89,7 @@ export class CoreShowPasswordComponent implements OnInit, AfterViewInit {
|
|||
/**
|
||||
* Set label, icon name and input type.
|
||||
*/
|
||||
protected setData() {
|
||||
protected setData(): void {
|
||||
this.label = this.shown ? 'core.hide' : 'core.show';
|
||||
this.iconName = this.shown ? 'eye-off' : 'eye';
|
||||
if (this.input) {
|
||||
|
@ -100,7 +100,7 @@ export class CoreShowPasswordComponent implements OnInit, AfterViewInit {
|
|||
/**
|
||||
* Toggle show/hide password.
|
||||
*/
|
||||
toggle() : void {
|
||||
toggle(): void {
|
||||
this.shown = !this.shown;
|
||||
this.setData();
|
||||
}
|
||||
|
|
|
@ -36,16 +36,16 @@ export class CoreSitePickerComponent implements OnInit {
|
|||
sites: any[];
|
||||
|
||||
constructor(private translate: TranslateService, private sitesProvider: CoreSitesProvider,
|
||||
private textUtils: CoreTextUtilsProvider) {
|
||||
private textUtils: CoreTextUtilsProvider) {
|
||||
this.siteSelected = new EventEmitter();
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
ngOnInit(): void {
|
||||
this.selectedSite = this.initialSite || this.sitesProvider.getCurrentSiteId();
|
||||
|
||||
// Load the sites.
|
||||
this.sitesProvider.getSites().then((sites) => {
|
||||
let promises = [];
|
||||
const promises = [];
|
||||
|
||||
sites.forEach((site: any) => {
|
||||
// Format the site name.
|
||||
|
@ -53,7 +53,7 @@ export class CoreSitePickerComponent implements OnInit {
|
|||
return site.siteName;
|
||||
}).then((formatted) => {
|
||||
site.fullNameAndSiteName = this.translate.instant('core.fullnameandsitename',
|
||||
{fullname: site.fullName, sitename: formatted});
|
||||
{ fullname: site.fullName, sitename: formatted });
|
||||
}));
|
||||
});
|
||||
|
||||
|
|
|
@ -17,13 +17,15 @@
|
|||
import { Component } from '@angular/core';
|
||||
import { IonicPage } from 'ionic-angular';
|
||||
|
||||
@IonicPage({segment: "core-placeholder"})
|
||||
@IonicPage({ segment: 'core-placeholder' })
|
||||
@Component({
|
||||
selector: 'core-placeholder',
|
||||
templateUrl: 'placeholder.html',
|
||||
})
|
||||
export class CoreSplitViewPlaceholderPage {
|
||||
|
||||
constructor() { }
|
||||
constructor() {
|
||||
// Nothing to do.
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -14,9 +14,8 @@
|
|||
|
||||
// Code based on https://github.com/martinpritchardelevate/ionic-split-pane-demo
|
||||
|
||||
import { Component, ViewChild, Injectable, Input, ElementRef, OnInit } from '@angular/core';
|
||||
import { Component, ViewChild, Input, ElementRef, OnInit } from '@angular/core';
|
||||
import { NavController, Nav } from 'ionic-angular';
|
||||
import { CoreSplitViewPlaceholderPage } from './placeholder/placeholder';
|
||||
|
||||
/**
|
||||
* Directive to create a split view layout.
|
||||
|
@ -45,9 +44,9 @@ 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 = "";
|
||||
@Input() when?: string | boolean = 'md';
|
||||
protected isEnabled = false;
|
||||
protected masterPageName = '';
|
||||
protected loadDetailPage: any = false;
|
||||
protected element: HTMLElement; // Current element.
|
||||
|
||||
|
@ -61,7 +60,7 @@ export class CoreSplitViewComponent implements OnInit {
|
|||
/**
|
||||
* Component being initialized.
|
||||
*/
|
||||
ngOnInit() {
|
||||
ngOnInit(): void {
|
||||
// Get the master page name and set an empty page as a placeholder.
|
||||
this.masterPageName = this.masterNav.getActive().component.name;
|
||||
this.emptyDetails();
|
||||
|
@ -82,7 +81,7 @@ export class CoreSplitViewComponent implements OnInit {
|
|||
* @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) {
|
||||
push(page: any, params?: any, element?: HTMLElement): void {
|
||||
if (this.isEnabled) {
|
||||
this.detailNav.setRoot(page, params);
|
||||
} else {
|
||||
|
@ -97,7 +96,7 @@ export class CoreSplitViewComponent implements OnInit {
|
|||
/**
|
||||
* Set the details panel to default info.
|
||||
*/
|
||||
emptyDetails() {
|
||||
emptyDetails(): void {
|
||||
this.loadDetailPage = false;
|
||||
this.detailNav.setRoot('CoreSplitViewPlaceholderPage');
|
||||
}
|
||||
|
@ -107,7 +106,7 @@ export class CoreSplitViewComponent implements OnInit {
|
|||
*
|
||||
* @param {Boolean} isOn If it fits both panels at the same time.
|
||||
*/
|
||||
onSplitPaneChanged(isOn) {
|
||||
onSplitPaneChanged(isOn: boolean): void {
|
||||
this.isEnabled = isOn;
|
||||
if (this.masterNav && this.detailNav) {
|
||||
(isOn) ? this.activateSplitView() : this.deactivateSplitView();
|
||||
|
@ -117,14 +116,14 @@ export class CoreSplitViewComponent implements OnInit {
|
|||
/**
|
||||
* Enable the split view, show both panels and do some magical navigation.
|
||||
*/
|
||||
activateSplitView() {
|
||||
let currentView = this.masterNav.getActive(),
|
||||
activateSplitView(): void {
|
||||
const 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.
|
||||
// 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.
|
||||
|
@ -136,12 +135,12 @@ export class CoreSplitViewComponent implements OnInit {
|
|||
/**
|
||||
* Disabled the split view, show only one panel and do some magical navigation.
|
||||
*/
|
||||
deactivateSplitView() {
|
||||
let detailView = this.detailNav.getActive(),
|
||||
deactivateSplitView(): void {
|
||||
const 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,8 +12,7 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component, Input, Output, OnInit, OnDestroy, ElementRef, EventEmitter, ContentChild, TemplateRef,
|
||||
ViewChild } from '@angular/core';
|
||||
import { Component, Input, Output, OnInit, OnDestroy, ElementRef, EventEmitter, ContentChild, TemplateRef } from '@angular/core';
|
||||
import { CoreTabsComponent } from './tabs';
|
||||
import { Content } from 'ionic-angular';
|
||||
|
||||
|
@ -45,12 +44,12 @@ export class CoreTabComponent implements OnInit, OnDestroy {
|
|||
@Input() icon?: string; // The tab icon.
|
||||
@Input() badge?: string; // A badge to add in the tab.
|
||||
@Input() badgeStyle?: string; // The badge color.
|
||||
@Input() enabled?: boolean = true; // Whether the tab is enabled.
|
||||
@Input() show?: boolean = true; // Whether the tab should be shown.
|
||||
@Input() enabled? = true; // Whether the tab is enabled.
|
||||
@Input() show? = true; // Whether the tab should be shown.
|
||||
@Input() id?: string; // An ID to identify the tab.
|
||||
@Output() ionSelect: EventEmitter<CoreTabComponent> = new EventEmitter<CoreTabComponent>();
|
||||
|
||||
@ContentChild(TemplateRef) template: TemplateRef<any> // Template defined by the content.
|
||||
@ContentChild(TemplateRef) template: TemplateRef<any>; // Template defined by the content.
|
||||
@ContentChild(Content) scroll: Content;
|
||||
|
||||
element: HTMLElement; // The core-tab element.
|
||||
|
@ -63,21 +62,21 @@ export class CoreTabComponent implements OnInit, OnDestroy {
|
|||
/**
|
||||
* Component being initialized.
|
||||
*/
|
||||
ngOnInit() {
|
||||
ngOnInit(): void {
|
||||
this.tabs.addTab(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Component destroyed.
|
||||
*/
|
||||
ngOnDestroy() {
|
||||
ngOnDestroy(): void {
|
||||
this.tabs.removeTab(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Select tab.
|
||||
*/
|
||||
selectTab() {
|
||||
selectTab(): void {
|
||||
this.element.classList.add('selected');
|
||||
|
||||
this.loaded = true;
|
||||
|
@ -86,9 +85,9 @@ export class CoreTabComponent implements OnInit, OnDestroy {
|
|||
// Setup tab scrolling.
|
||||
setTimeout(() => {
|
||||
if (this.scroll) {
|
||||
this.scroll.getScrollElement().onscroll = (e) => {
|
||||
this.scroll.getScrollElement().onscroll = (e): void => {
|
||||
this.tabs.showHideTabs(e);
|
||||
}
|
||||
};
|
||||
}
|
||||
}, 1);
|
||||
}
|
||||
|
@ -96,7 +95,7 @@ export class CoreTabComponent implements OnInit, OnDestroy {
|
|||
/**
|
||||
* Unselect tab.
|
||||
*/
|
||||
unselectTab() {
|
||||
unselectTab(): void {
|
||||
this.element.classList.remove('selected');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,8 +12,10 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component, Input, Output, EventEmitter, OnInit, OnChanges, AfterViewInit, ViewChild, ElementRef,
|
||||
SimpleChange } from '@angular/core';
|
||||
import {
|
||||
Component, Input, Output, EventEmitter, OnInit, OnChanges, AfterViewInit, ViewChild, ElementRef,
|
||||
SimpleChange
|
||||
} from '@angular/core';
|
||||
import { CoreTabComponent } from './tab';
|
||||
import { Content } from 'ionic-angular';
|
||||
|
||||
|
@ -40,7 +42,7 @@ import { Content } from 'ionic-angular';
|
|||
templateUrl: 'tabs.html'
|
||||
})
|
||||
export class CoreTabsComponent implements OnInit, AfterViewInit, OnChanges {
|
||||
@Input() selectedIndex?: number = 0; // Index of the tab to select.
|
||||
@Input() selectedIndex = 0; // Index of the tab to select.
|
||||
@Input() hideUntil: boolean; // Determine when should the contents be shown.
|
||||
@Output() ionChange: EventEmitter<CoreTabComponent> = new EventEmitter<CoreTabComponent>(); // Emitted when the tab changes.
|
||||
@ViewChild('originalTabs') originalTabsRef: ElementRef;
|
||||
|
@ -70,7 +72,7 @@ export class CoreTabsComponent implements OnInit, AfterViewInit, OnChanges {
|
|||
/**
|
||||
* Component being initialized.
|
||||
*/
|
||||
ngOnInit() {
|
||||
ngOnInit(): void {
|
||||
this.originalTabsContainer = this.originalTabsRef.nativeElement;
|
||||
this.topTabsElement = this.topTabs.nativeElement;
|
||||
}
|
||||
|
@ -78,7 +80,7 @@ export class CoreTabsComponent implements OnInit, AfterViewInit, OnChanges {
|
|||
/**
|
||||
* View has been initialized.
|
||||
*/
|
||||
ngAfterViewInit() {
|
||||
ngAfterViewInit(): void {
|
||||
this.afterViewInitTriggered = true;
|
||||
if (!this.initialized && this.hideUntil) {
|
||||
// Tabs should be shown, initialize them.
|
||||
|
@ -89,7 +91,7 @@ export class CoreTabsComponent implements OnInit, AfterViewInit, OnChanges {
|
|||
/**
|
||||
* Detect changes on input properties.
|
||||
*/
|
||||
ngOnChanges(changes: {[name: string]: SimpleChange}) {
|
||||
ngOnChanges(changes: { [name: string]: SimpleChange }): void {
|
||||
// We need to wait for ngAfterViewInit because we need core-tab components to be executed.
|
||||
if (!this.initialized && this.hideUntil && this.afterViewInitTriggered) {
|
||||
// Tabs should be shown, initialize them.
|
||||
|
@ -105,7 +107,7 @@ export class CoreTabsComponent implements OnInit, AfterViewInit, OnChanges {
|
|||
*
|
||||
* @param {CoreTabComponent} tab The tab to add.
|
||||
*/
|
||||
addTab(tab: CoreTabComponent) : void {
|
||||
addTab(tab: CoreTabComponent): void {
|
||||
// Check if tab is already in the list.
|
||||
if (this.getIndex(tab) == -1) {
|
||||
this.tabs.push(tab);
|
||||
|
@ -119,13 +121,14 @@ export class CoreTabsComponent implements OnInit, AfterViewInit, OnChanges {
|
|||
* @param {any} tab [description]
|
||||
* @return {number} [description]
|
||||
*/
|
||||
getIndex(tab: any) : number {
|
||||
getIndex(tab: any): number {
|
||||
for (let i = 0; i < this.tabs.length; i++) {
|
||||
let t = this.tabs[i];
|
||||
const t = this.tabs[i];
|
||||
if (t === tab || (typeof t.id != 'undefined' && t.id === tab.id)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
@ -134,14 +137,14 @@ export class CoreTabsComponent implements OnInit, AfterViewInit, OnChanges {
|
|||
*
|
||||
* @return {CoreTabComponent} Selected tab.
|
||||
*/
|
||||
getSelected() : CoreTabComponent {
|
||||
getSelected(): CoreTabComponent {
|
||||
return this.tabs[this.selected];
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the tabs, determining the first tab to be shown.
|
||||
*/
|
||||
protected initializeTabs() : void {
|
||||
protected initializeTabs(): void {
|
||||
let selectedIndex = this.selectedIndex || 0,
|
||||
selectedTab = this.tabs[selectedIndex];
|
||||
|
||||
|
@ -150,8 +153,10 @@ export class CoreTabsComponent implements OnInit, AfterViewInit, OnChanges {
|
|||
selectedTab = this.tabs.find((tab, index) => {
|
||||
if (tab.enabled && tab.show) {
|
||||
selectedIndex = index;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
@ -162,7 +167,7 @@ export class CoreTabsComponent implements OnInit, AfterViewInit, OnChanges {
|
|||
|
||||
// Setup tab scrolling.
|
||||
this.tabBarHeight = this.topTabsElement.offsetHeight;
|
||||
this.originalTabsContainer.style.paddingBottom = this.tabBarHeight + 'px';
|
||||
this.originalTabsContainer.style.paddingBottom = this.tabBarHeight + 'px';
|
||||
if (this.scroll) {
|
||||
this.scroll.classList.add('no-scroll');
|
||||
}
|
||||
|
@ -175,7 +180,7 @@ export class CoreTabsComponent implements OnInit, AfterViewInit, OnChanges {
|
|||
*
|
||||
* @param {any} e Scroll event.
|
||||
*/
|
||||
showHideTabs(e: any) : void {
|
||||
showHideTabs(e: any): void {
|
||||
if (e.target.scrollTop < this.tabBarHeight) {
|
||||
if (!this.tabsShown) {
|
||||
this.tabBarElement.classList.remove('tabs-hidden');
|
||||
|
@ -194,7 +199,7 @@ export class CoreTabsComponent implements OnInit, AfterViewInit, OnChanges {
|
|||
*
|
||||
* @param {CoreTabComponent} tab The tab to remove.
|
||||
*/
|
||||
removeTab(tab: CoreTabComponent) : void {
|
||||
removeTab(tab: CoreTabComponent): void {
|
||||
const index = this.getIndex(tab);
|
||||
this.tabs.splice(index, 1);
|
||||
}
|
||||
|
@ -204,7 +209,7 @@ export class CoreTabsComponent implements OnInit, AfterViewInit, OnChanges {
|
|||
*
|
||||
* @param {number} index The index of the tab to select.
|
||||
*/
|
||||
selectTab(index: number) : void {
|
||||
selectTab(index: number): void {
|
||||
if (index == this.selected) {
|
||||
// Already selected.
|
||||
return;
|
||||
|
@ -236,13 +241,13 @@ export class CoreTabsComponent implements OnInit, AfterViewInit, OnChanges {
|
|||
/**
|
||||
* Sort the tabs, keeping the same order as in the original list.
|
||||
*/
|
||||
protected sortTabs() {
|
||||
protected sortTabs(): void {
|
||||
if (this.originalTabsContainer) {
|
||||
let newTabs = [],
|
||||
newSelected;
|
||||
const newTabs = [];
|
||||
let newSelected;
|
||||
|
||||
this.tabs.forEach((tab, index) => {
|
||||
let originalIndex = Array.prototype.indexOf.call(this.originalTabsContainer.children, tab.element);
|
||||
const originalIndex = Array.prototype.indexOf.call(this.originalTabsContainer.children, tab.element);
|
||||
if (originalIndex != -1) {
|
||||
newTabs[originalIndex] = tab;
|
||||
if (this.selected == index) {
|
||||
|
|
|
@ -16,34 +16,34 @@
|
|||
* Static class to contain all the core constants.
|
||||
*/
|
||||
export class CoreConstants {
|
||||
public static SECONDS_YEAR = 31536000;
|
||||
public static SECONDS_WEEK = 604800;
|
||||
public static SECONDS_DAY = 86400;
|
||||
public static SECONDS_HOUR = 3600;
|
||||
public static SECONDS_MINUTE = 60;
|
||||
public static WIFI_DOWNLOAD_THRESHOLD = 104857600; // 100MB.
|
||||
public static DOWNLOAD_THRESHOLD = 10485760; // 10MB.
|
||||
public static DONT_SHOW_ERROR = 'CoreDontShowError';
|
||||
public static NO_SITE_ID = 'NoSite';
|
||||
static SECONDS_YEAR = 31536000;
|
||||
static SECONDS_WEEK = 604800;
|
||||
static SECONDS_DAY = 86400;
|
||||
static SECONDS_HOUR = 3600;
|
||||
static SECONDS_MINUTE = 60;
|
||||
static WIFI_DOWNLOAD_THRESHOLD = 104857600; // 100MB.
|
||||
static DOWNLOAD_THRESHOLD = 10485760; // 10MB.
|
||||
static DONT_SHOW_ERROR = 'CoreDontShowError';
|
||||
static NO_SITE_ID = 'NoSite';
|
||||
|
||||
// Settings constants.
|
||||
public static SETTINGS_RICH_TEXT_EDITOR = 'CoreSettingsRichTextEditor';
|
||||
public static SETTINGS_NOTIFICATION_SOUND = 'CoreSettingsNotificationSound';
|
||||
public static SETTINGS_SYNC_ONLY_ON_WIFI = 'CoreSettingsSyncOnlyOnWifi';
|
||||
static SETTINGS_RICH_TEXT_EDITOR = 'CoreSettingsRichTextEditor';
|
||||
static SETTINGS_NOTIFICATION_SOUND = 'CoreSettingsNotificationSound';
|
||||
static SETTINGS_SYNC_ONLY_ON_WIFI = 'CoreSettingsSyncOnlyOnWifi';
|
||||
|
||||
// WS constants.
|
||||
public static WS_TIMEOUT = 30000;
|
||||
public static WS_PREFIX = 'local_mobile_';
|
||||
static WS_TIMEOUT = 30000;
|
||||
static WS_PREFIX = 'local_mobile_';
|
||||
|
||||
// Login constants.
|
||||
public static LOGIN_SSO_CODE = 2; // SSO in browser window is required.
|
||||
public static LOGIN_SSO_INAPP_CODE = 3; // SSO in embedded browser is required.
|
||||
public static LOGIN_LAUNCH_DATA = 'CoreLoginLaunchData';
|
||||
static LOGIN_SSO_CODE = 2; // SSO in browser window is required.
|
||||
static LOGIN_SSO_INAPP_CODE = 3; // SSO in embedded browser is required.
|
||||
static LOGIN_LAUNCH_DATA = 'CoreLoginLaunchData';
|
||||
|
||||
// Download status constants.
|
||||
public static DOWNLOADED = 'downloaded';
|
||||
public static DOWNLOADING = 'downloading';
|
||||
public static NOT_DOWNLOADED = 'notdownloaded';
|
||||
public static OUTDATED = 'outdated';
|
||||
public static NOT_DOWNLOADABLE = 'notdownloadable';
|
||||
static DOWNLOADED = 'downloaded';
|
||||
static DOWNLOADING = 'downloading';
|
||||
static NOT_DOWNLOADED = 'notdownloaded';
|
||||
static OUTDATED = 'outdated';
|
||||
static NOT_DOWNLOADABLE = 'notdownloadable';
|
||||
}
|
||||
|
|
|
@ -54,7 +54,9 @@ export class CoreContentLinksHandlerBase implements CoreContentLinksHandler {
|
|||
*/
|
||||
pattern?: RegExp;
|
||||
|
||||
constructor() {}
|
||||
constructor() {
|
||||
// Nothing to do.
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of actions for a link (url).
|
||||
|
@ -65,8 +67,8 @@ export class CoreContentLinksHandlerBase implements CoreContentLinksHandler {
|
|||
* @param {number} [courseId] Course ID related to the URL. Optional but recommended.
|
||||
* @return {CoreContentLinksAction[]|Promise<CoreContentLinksAction[]>} List of (or promise resolved with list of) actions.
|
||||
*/
|
||||
getActions(siteIds: string[], url: string, params: any, courseId?: number) :
|
||||
CoreContentLinksAction[]|Promise<CoreContentLinksAction[]> {
|
||||
getActions(siteIds: string[], url: string, params: any, courseId?: number):
|
||||
CoreContentLinksAction[] | Promise<CoreContentLinksAction[]> {
|
||||
return [];
|
||||
}
|
||||
|
||||
|
@ -76,7 +78,7 @@ export class CoreContentLinksHandlerBase implements CoreContentLinksHandler {
|
|||
* @param {string} url The URL to check.
|
||||
* @return {boolean} Whether the URL is handled by this handler
|
||||
*/
|
||||
handles(url: string) : boolean {
|
||||
handles(url: string): boolean {
|
||||
return this.pattern && url.search(this.pattern) >= 0;
|
||||
}
|
||||
|
||||
|
@ -86,9 +88,9 @@ export class CoreContentLinksHandlerBase implements CoreContentLinksHandler {
|
|||
* @param {string} url The URL to check.
|
||||
* @return {string} Site URL if it is handled, undefined otherwise.
|
||||
*/
|
||||
getSiteUrl(url: string) : string {
|
||||
getSiteUrl(url: string): string {
|
||||
if (this.pattern) {
|
||||
var position = url.search(this.pattern);
|
||||
const position = url.search(this.pattern);
|
||||
if (position > -1) {
|
||||
return url.substr(0, position);
|
||||
}
|
||||
|
@ -105,7 +107,7 @@ export class CoreContentLinksHandlerBase implements CoreContentLinksHandler {
|
|||
* @param {number} [courseId] Course ID related to the URL. Optional but recommended.
|
||||
* @return {boolean|Promise<boolean>} Whether the handler is enabled for the URL and site.
|
||||
*/
|
||||
isEnabled(siteId: string, url: string, params: any, courseId?: number) : boolean|Promise<boolean> {
|
||||
isEnabled(siteId: string, url: string, params: any, courseId?: number): boolean | Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,12 +60,13 @@ export class CoreContentLinksModuleGradeHandler extends CoreContentLinksHandlerB
|
|||
* @param {number} [courseId] Course ID related to the URL. Optional but recommended.
|
||||
* @return {CoreContentLinksAction[]|Promise<CoreContentLinksAction[]>} List of (or promise resolved with list of) actions.
|
||||
*/
|
||||
getActions(siteIds: string[], url: string, params: any, courseId?: number) :
|
||||
CoreContentLinksAction[]|Promise<CoreContentLinksAction[]> {
|
||||
getActions(siteIds: string[], url: string, params: any, courseId?: number):
|
||||
CoreContentLinksAction[] | Promise<CoreContentLinksAction[]> {
|
||||
|
||||
courseId = courseId || params.courseid || params.cid;
|
||||
|
||||
return [{
|
||||
action: (siteId, navCtrl?) : void => {
|
||||
action: (siteId, navCtrl?): void => {
|
||||
// Check if userid is the site's current user.
|
||||
const modal = this.domUtils.showModalLoading();
|
||||
this.sitesProvider.getSite(siteId).then((site) => {
|
||||
|
@ -96,7 +97,7 @@ export class CoreContentLinksModuleGradeHandler extends CoreContentLinksHandlerB
|
|||
* @param {NavController} [navCtrl] Nav Controller to use to navigate.
|
||||
* @return {Promise<any>} Promise resolved when done.
|
||||
*/
|
||||
protected goToReview(url: string, params: any, courseId: number, siteId: string, navCtrl?: NavController) : Promise<any> {
|
||||
protected goToReview(url: string, params: any, courseId: number, siteId: string, navCtrl?: NavController): Promise<any> {
|
||||
// This function should be overridden.
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
|
|
@ -50,12 +50,13 @@ export class CoreContentLinksModuleIndexHandler extends CoreContentLinksHandlerB
|
|||
* @param {number} [courseId] Course ID related to the URL. Optional but recommended.
|
||||
* @return {CoreContentLinksAction[]|Promise<CoreContentLinksAction[]>} List of (or promise resolved with list of) actions.
|
||||
*/
|
||||
getActions(siteIds: string[], url: string, params: any, courseId?: number) :
|
||||
CoreContentLinksAction[]|Promise<CoreContentLinksAction[]> {
|
||||
getActions(siteIds: string[], url: string, params: any, courseId?: number):
|
||||
CoreContentLinksAction[] | Promise<CoreContentLinksAction[]> {
|
||||
|
||||
courseId = courseId || params.courseid || params.cid;
|
||||
|
||||
return [{
|
||||
action: (siteId, navCtrl?) => {
|
||||
action: (siteId, navCtrl?): void => {
|
||||
this.courseHelper.navigateToModule(parseInt(params.id, 10), siteId, courseId);
|
||||
}
|
||||
}];
|
||||
|
|
|
@ -22,7 +22,7 @@ import { CoreContentLinksHelperProvider } from '../../providers/helper';
|
|||
/**
|
||||
* Page to display the list of sites to choose one to perform a content link action.
|
||||
*/
|
||||
@IonicPage({segment: 'core-content-links-choose-site'})
|
||||
@IonicPage({ segment: 'core-content-links-choose-site' })
|
||||
@Component({
|
||||
selector: 'page-core-content-links-choose-site',
|
||||
templateUrl: 'choose-site.html',
|
||||
|
@ -43,7 +43,7 @@ export class CoreContentLinksChooseSitePage implements OnInit {
|
|||
/**
|
||||
* Component being initialized.
|
||||
*/
|
||||
ngOnInit() {
|
||||
ngOnInit(): void {
|
||||
if (!this.url) {
|
||||
return this.leaveView();
|
||||
}
|
||||
|
@ -70,7 +70,7 @@ export class CoreContentLinksChooseSitePage implements OnInit {
|
|||
/**
|
||||
* Cancel.
|
||||
*/
|
||||
cancel() : void {
|
||||
cancel(): void {
|
||||
this.leaveView();
|
||||
}
|
||||
|
||||
|
@ -79,17 +79,16 @@ export class CoreContentLinksChooseSitePage implements OnInit {
|
|||
*
|
||||
* @param {string} siteId Site ID.
|
||||
*/
|
||||
siteClicked(siteId: string) : void {
|
||||
siteClicked(siteId: string): void {
|
||||
this.action.action(siteId, this.navCtrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel and leave the view.
|
||||
*/
|
||||
protected leaveView() {
|
||||
protected leaveView(): void {
|
||||
this.sitesProvider.logout().finally(() => {
|
||||
this.navCtrl.setRoot('CoreLoginSitesPage');
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -58,8 +58,8 @@ export interface CoreContentLinksHandler {
|
|||
* @param {number} [courseId] Course ID related to the URL. Optional but recommended.
|
||||
* @return {CoreContentLinksAction[]|Promise<CoreContentLinksAction[]>} List of (or promise resolved with list of) actions.
|
||||
*/
|
||||
getActions(siteIds: string[], url: string, params: any, courseId?: number) :
|
||||
CoreContentLinksAction[]|Promise<CoreContentLinksAction[]>;
|
||||
getActions(siteIds: string[], url: string, params: any, courseId?: number):
|
||||
CoreContentLinksAction[] | Promise<CoreContentLinksAction[]>;
|
||||
|
||||
/**
|
||||
* Check if a URL is handled by this handler.
|
||||
|
@ -67,7 +67,7 @@ export interface CoreContentLinksHandler {
|
|||
* @param {string} url The URL to check.
|
||||
* @return {boolean} Whether the URL is handled by this handler
|
||||
*/
|
||||
handles(url: string) : boolean;
|
||||
handles(url: string): boolean;
|
||||
|
||||
/**
|
||||
* If the URL is handled by this handler, return the site URL.
|
||||
|
@ -75,7 +75,7 @@ export interface CoreContentLinksHandler {
|
|||
* @param {string} url The URL to check.
|
||||
* @return {string} Site URL if it is handled, undefined otherwise.
|
||||
*/
|
||||
getSiteUrl(url: string) : string;
|
||||
getSiteUrl(url: string): string;
|
||||
|
||||
/**
|
||||
* Check if the handler is enabled for a certain site (site + user) and a URL.
|
||||
|
@ -87,8 +87,8 @@ export interface CoreContentLinksHandler {
|
|||
* @param {number} [courseId] Course ID related to the URL. Optional but recommended.
|
||||
* @return {boolean|Promise<boolean>} Whether the handler is enabled for the URL and site.
|
||||
*/
|
||||
isEnabled?(siteId: string, url: string, params: any, courseId?: number) : boolean|Promise<boolean>;
|
||||
};
|
||||
isEnabled?(siteId: string, url: string, params: any, courseId?: number): boolean | Promise<boolean>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Action to perform when a link is clicked.
|
||||
|
@ -118,8 +118,8 @@ export interface CoreContentLinksAction {
|
|||
* @param {string} siteId The site ID.
|
||||
* @param {NavController} [navCtrl] Nav Controller to use to navigate.
|
||||
*/
|
||||
action(siteId: string, navCtrl?: NavController) : void;
|
||||
};
|
||||
action(siteId: string, navCtrl?: NavController): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Actions and priority for a handler and URL.
|
||||
|
@ -136,7 +136,7 @@ export interface CoreContentLinksHandlerActions {
|
|||
* @type {CoreContentLinksAction[]}
|
||||
*/
|
||||
actions: CoreContentLinksAction[];
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegate to register handlers to handle links.
|
||||
|
@ -144,7 +144,7 @@ export interface CoreContentLinksHandlerActions {
|
|||
@Injectable()
|
||||
export class CoreContentLinksDelegate {
|
||||
protected logger;
|
||||
protected handlers: {[s: string]: CoreContentLinksHandler} = {}; // All registered handlers.
|
||||
protected handlers: { [s: string]: CoreContentLinksHandler } = {}; // All registered handlers.
|
||||
|
||||
constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private urlUtils: CoreUrlUtilsProvider,
|
||||
private utils: CoreUtilsProvider) {
|
||||
|
@ -159,7 +159,7 @@ export class CoreContentLinksDelegate {
|
|||
* @param {string} [username] Username to use to filter sites.
|
||||
* @return {Promise<CoreContentLinksAction[]>} Promise resolved with the actions.
|
||||
*/
|
||||
getActionsFor(url: string, courseId?: number, username?: string) : Promise<CoreContentLinksAction[]> {
|
||||
getActionsFor(url: string, courseId?: number, username?: string): Promise<CoreContentLinksAction[]> {
|
||||
if (!url) {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
|
@ -170,7 +170,7 @@ export class CoreContentLinksDelegate {
|
|||
promises = [],
|
||||
params = this.urlUtils.extractUrlParams(url);
|
||||
|
||||
for (let name in this.handlers) {
|
||||
for (const name in this.handlers) {
|
||||
const handler = this.handlers[name],
|
||||
checkAll = handler.checkAllUsers,
|
||||
isEnabledFn = this.isHandlerEnabled.bind(this, handler, url, params, courseId);
|
||||
|
@ -192,8 +192,8 @@ export class CoreContentLinksDelegate {
|
|||
// Set default values if any value isn't supplied.
|
||||
actions.forEach((action) => {
|
||||
action.message = action.message || 'core.view';
|
||||
action.icon = action.icon || 'eye';
|
||||
action.sites = action.sites || siteIds;
|
||||
action.icon = action.icon || 'eye';
|
||||
action.sites = action.sites || siteIds;
|
||||
});
|
||||
|
||||
// Add them to the list.
|
||||
|
@ -221,13 +221,13 @@ export class CoreContentLinksDelegate {
|
|||
* @param {string} url URL to handle.
|
||||
* @return {string} Site URL if the URL is supported by any handler, undefined otherwise.
|
||||
*/
|
||||
getSiteUrl(url: string) : string {
|
||||
getSiteUrl(url: string): string {
|
||||
if (!url) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if any handler supports this URL.
|
||||
for (let name in this.handlers) {
|
||||
for (const name in this.handlers) {
|
||||
const handler = this.handlers[name],
|
||||
siteUrl = handler.getSiteUrl(url);
|
||||
|
||||
|
@ -264,7 +264,7 @@ export class CoreContentLinksDelegate {
|
|||
}
|
||||
|
||||
if (!handler.isEnabled) {
|
||||
// isEnabled function not provided, assume it's enabled.
|
||||
// Handler doesn't implement isEnabled, assume it's enabled.
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -278,13 +278,15 @@ export class CoreContentLinksDelegate {
|
|||
* @param {CoreContentLinksHandler} handler The handler to register.
|
||||
* @return {boolean} True if registered successfully, false otherwise.
|
||||
*/
|
||||
registerHandler(handler: CoreContentLinksHandler) : boolean {
|
||||
registerHandler(handler: CoreContentLinksHandler): boolean {
|
||||
if (typeof this.handlers[handler.name] !== 'undefined') {
|
||||
this.logger.log(`Addon '${handler.name}' already registered`);
|
||||
|
||||
return false;
|
||||
}
|
||||
this.logger.log(`Registered addon '${handler.name}'`);
|
||||
this.handlers[handler.name] = handler;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -294,7 +296,7 @@ export class CoreContentLinksDelegate {
|
|||
* @param {CoreContentLinksHandlerActions[]} actions Actions to sort.
|
||||
* @return {CoreContentLinksAction[]} Sorted actions.
|
||||
*/
|
||||
protected sortActionsByPriority(actions: CoreContentLinksHandlerActions[]) : CoreContentLinksAction[] {
|
||||
protected sortActionsByPriority(actions: CoreContentLinksHandlerActions[]): CoreContentLinksAction[] {
|
||||
let sorted: CoreContentLinksAction[] = [];
|
||||
|
||||
// Sort by priority.
|
||||
|
@ -306,6 +308,7 @@ export class CoreContentLinksDelegate {
|
|||
actions.forEach((entry) => {
|
||||
sorted = sorted.concat(entry.actions);
|
||||
});
|
||||
|
||||
return sorted;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,7 +50,7 @@ export class CoreContentLinksHelperProvider {
|
|||
* @param {CoreContentLinksAction[]} actions List of actions.
|
||||
* @return {CoreContentLinksAction} First valid action. Returns undefined if no valid action found.
|
||||
*/
|
||||
getFirstValidAction(actions: CoreContentLinksAction[]) : CoreContentLinksAction {
|
||||
getFirstValidAction(actions: CoreContentLinksAction[]): CoreContentLinksAction {
|
||||
if (actions) {
|
||||
for (let i = 0; i < actions.length; i++) {
|
||||
const action = actions[i];
|
||||
|
@ -70,9 +70,9 @@ export class CoreContentLinksHelperProvider {
|
|||
* @param {any} [pageParams] Params to send to the page.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
*/
|
||||
goInSite(navCtrl: NavController, pageName: string, pageParams: any, siteId?: string) : void {
|
||||
goInSite(navCtrl: NavController, pageName: string, pageParams: any, siteId?: string): void {
|
||||
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||
if (siteId == this.sitesProvider.getCurrentSiteId()) {
|
||||
if (navCtrl && siteId == this.sitesProvider.getCurrentSiteId()) {
|
||||
navCtrl.push(pageName, pageParams);
|
||||
} else {
|
||||
this.loginHelper.redirect(pageName, pageParams, siteId);
|
||||
|
@ -84,8 +84,8 @@ export class CoreContentLinksHelperProvider {
|
|||
*
|
||||
* @param {string} url URL to treat.
|
||||
*/
|
||||
goToChooseSite(url: string) : void {
|
||||
this.appProvider.getRootNavController().setRoot('CoreContentLinksChooseSitePage', {url: url});
|
||||
goToChooseSite(url: string): void {
|
||||
this.appProvider.getRootNavController().setRoot('CoreContentLinksChooseSitePage', { url: url });
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -94,20 +94,20 @@ export class CoreContentLinksHelperProvider {
|
|||
* @param {string} url URL to handle.
|
||||
* @return {boolean} True if the URL should be handled by this component, false otherwise.
|
||||
*/
|
||||
handleCustomUrl(url: string) : boolean {
|
||||
handleCustomUrl(url: string): boolean {
|
||||
const contentLinksScheme = CoreConfigConstants.customurlscheme + '://link';
|
||||
if (url.indexOf(contentLinksScheme) == -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const modal = this.domUtils.showModalLoading();
|
||||
let username;
|
||||
|
||||
url = decodeURIComponent(url);
|
||||
|
||||
// App opened using custom URL scheme.
|
||||
this.logger.debug('Treating custom URL scheme: ' + url);
|
||||
|
||||
let modal = this.domUtils.showModalLoading(),
|
||||
username;
|
||||
|
||||
// Delete the scheme from the URL.
|
||||
url = url.replace(contentLinksScheme + '=', '');
|
||||
|
||||
|
@ -124,6 +124,7 @@ export class CoreContentLinksHelperProvider {
|
|||
}).then((siteIds) => {
|
||||
if (siteIds.length) {
|
||||
modal.dismiss(); // Dismiss modal so it doesn't collide with confirms.
|
||||
|
||||
return this.handleLink(url, username).then((treated) => {
|
||||
if (!treated) {
|
||||
this.domUtils.showErrorModal('core.contentlinks.errornoactions', true);
|
||||
|
@ -134,14 +135,14 @@ export class CoreContentLinksHelperProvider {
|
|||
const siteUrl = this.contentLinksDelegate.getSiteUrl(url);
|
||||
if (!siteUrl) {
|
||||
this.domUtils.showErrorModal('core.login.invalidsite', true);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Check that site exists.
|
||||
return this.sitesProvider.checkSite(siteUrl).then((result) => {
|
||||
// Site exists. We'll allow to add it.
|
||||
let promise,
|
||||
ssoNeeded = this.loginHelper.isSSOLoginNeeded(result.code),
|
||||
const ssoNeeded = this.loginHelper.isSSOLoginNeeded(result.code),
|
||||
hasRemoteAddonsLoaded = false,
|
||||
pageName = 'CoreLoginCredentialsPage',
|
||||
pageParams = {
|
||||
|
@ -150,6 +151,7 @@ export class CoreContentLinksHelperProvider {
|
|||
urlToOpen: url,
|
||||
siteConfig: result.config
|
||||
};
|
||||
let promise;
|
||||
|
||||
modal.dismiss(); // Dismiss modal so it doesn't collide with confirms.
|
||||
|
||||
|
@ -161,7 +163,7 @@ export class CoreContentLinksHelperProvider {
|
|||
const confirmMsg = this.translate.instant('core.contentlinks.confirmurlothersite');
|
||||
promise = this.domUtils.showConfirm(confirmMsg).then(() => {
|
||||
if (!ssoNeeded) {
|
||||
// hasRemoteAddonsLoaded = $mmAddonManager.hasRemoteAddonsLoaded(); @todo
|
||||
// @todo hasRemoteAddonsLoaded = $mmAddonManager.hasRemoteAddonsLoaded(); @todo
|
||||
if (hasRemoteAddonsLoaded) {
|
||||
// Store the redirect since logout will restart the app.
|
||||
this.appProvider.storeRedirect(CoreConstants.NO_SITE_ID, pageName, pageParams);
|
||||
|
@ -177,7 +179,7 @@ export class CoreContentLinksHelperProvider {
|
|||
return promise.then(() => {
|
||||
if (ssoNeeded) {
|
||||
this.loginHelper.confirmAndOpenBrowserForSSOLogin(
|
||||
result.siteUrl, result.code, result.service, result.config && result.config.launchurl);
|
||||
result.siteUrl, result.code, result.service, result.config && result.config.launchurl);
|
||||
} else if (!hasRemoteAddonsLoaded) {
|
||||
this.appProvider.getRootNavController().setRoot(pageName, pageParams);
|
||||
}
|
||||
|
@ -205,7 +207,7 @@ export class CoreContentLinksHelperProvider {
|
|||
* @param {NavController} [navCtrl] Nav Controller to use to navigate.
|
||||
* @return {Promise<boolean>} Promise resolved with a boolean: true if URL was treated, false otherwise.
|
||||
*/
|
||||
handleLink(url: string, username?: string, navCtrl?: NavController) : Promise<boolean> {
|
||||
handleLink(url: string, username?: string, navCtrl?: NavController): Promise<boolean> {
|
||||
// Check if the link should be treated by some component/addon.
|
||||
return this.contentLinksDelegate.getActionsFor(url, undefined, username).then((actions) => {
|
||||
const action = this.getFirstValidAction(actions);
|
||||
|
@ -230,8 +232,10 @@ export class CoreContentLinksHelperProvider {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}).catch(() => {
|
||||
return false;
|
||||
|
|
|
@ -37,7 +37,7 @@ import { CoreConstants } from '../../constants';
|
|||
* @return {Promise<string>} Promise resolved when the prefetch finishes. The string returned will be stored as "extra" data in the
|
||||
* filepool package. If you don't need to store extra data, don't return anything.
|
||||
*/
|
||||
export type prefetchFunction = (module: any, courseId: number, single: boolean, siteId: string, ...args) => Promise<string>;
|
||||
export type prefetchFunction = (module: any, courseId: number, single: boolean, siteId: string, ...args: any[]) => Promise<string>;
|
||||
|
||||
/**
|
||||
* Base prefetch handler to be registered in CoreCourseModulePrefetchDelegate. It is useful to minimize the amount of
|
||||
|
@ -88,10 +88,10 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref
|
|||
* List of download promises to prevent downloading the module twice at the same time.
|
||||
* @type {{[s: string]: {[s: string]: Promise<any>}}}
|
||||
*/
|
||||
protected downloadPromises: {[s: string]: {[s: string]: Promise<any>}} = {};
|
||||
protected downloadPromises: { [s: string]: { [s: string]: Promise<any> } } = {};
|
||||
|
||||
// List of services that will be injected using injector. It's done like this so subclasses don't have to send all the
|
||||
// services to the parent in the constructor.
|
||||
// List of services that will be injected using injector.
|
||||
// It's done like this so subclasses don't have to send all the services to the parent in the constructor.
|
||||
protected translate: TranslateService;
|
||||
protected appProvider: CoreAppProvider;
|
||||
protected courseProvider: CoreCourseProvider;
|
||||
|
@ -118,7 +118,7 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref
|
|||
* @param {String} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Promise of the current download.
|
||||
*/
|
||||
addOngoingDownload(id: number, promise: Promise<any>, siteId?: string) : Promise<any> {
|
||||
addOngoingDownload(id: number, promise: Promise<any>, siteId?: string): Promise<any> {
|
||||
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||
|
||||
const uniqueId = this.getUniqueId(id);
|
||||
|
@ -141,7 +141,7 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref
|
|||
* @param {number} courseId Course ID.
|
||||
* @return {Promise<any>} Promise resolved when all content is downloaded.
|
||||
*/
|
||||
download(module: any, courseId: number) : Promise<any> {
|
||||
download(module: any, courseId: number): Promise<any> {
|
||||
return this.downloadOrPrefetch(module, courseId, false);
|
||||
}
|
||||
|
||||
|
@ -156,7 +156,7 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref
|
|||
* in the filepool root folder.
|
||||
* @return {Promise<any>} Promise resolved when all content is downloaded. Data returned is not reliable.
|
||||
*/
|
||||
downloadOrPrefetch(module: any, courseId: number, prefetch?: boolean, dirPath?: string) : Promise<any> {
|
||||
downloadOrPrefetch(module: any, courseId: number, prefetch?: boolean, dirPath?: string): Promise<any> {
|
||||
if (!this.appProvider.isOnline()) {
|
||||
// Cannot download in offline.
|
||||
return Promise.reject(this.translate.instant('core.networkerrormsg'));
|
||||
|
@ -169,21 +169,21 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref
|
|||
// Get the intro files.
|
||||
return this.getIntroFiles(module, courseId);
|
||||
}).then((introFiles) => {
|
||||
let downloadFn = prefetch ? this.filepoolProvider.prefetchPackage.bind(this.filepoolProvider) :
|
||||
this.filepoolProvider.downloadPackage.bind(this.filepoolProvider),
|
||||
const downloadFn = prefetch ? this.filepoolProvider.prefetchPackage.bind(this.filepoolProvider) :
|
||||
this.filepoolProvider.downloadPackage.bind(this.filepoolProvider),
|
||||
contentFiles = this.getContentDownloadableFiles(module),
|
||||
promises = [];
|
||||
|
||||
if (dirPath) {
|
||||
// Download intro files in filepool root folder.
|
||||
promises.push(this.filepoolProvider.downloadOrPrefetchFiles(siteId, introFiles, prefetch, false,
|
||||
this.component, module.id));
|
||||
this.component, module.id));
|
||||
|
||||
// Download content files inside dirPath.
|
||||
promises.push(downloadFn(siteId, contentFiles, this.component, module.id, undefined, dirPath));
|
||||
} else {
|
||||
// No dirPath, download everything in filepool root folder.
|
||||
let files = introFiles.concat(contentFiles);
|
||||
const files = introFiles.concat(contentFiles);
|
||||
promises.push(downloadFn(siteId, files, this.component, module.id));
|
||||
}
|
||||
|
||||
|
@ -197,8 +197,8 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref
|
|||
* @param {any} module The module object returned by WS.
|
||||
* @return {any[]} List of files.
|
||||
*/
|
||||
getContentDownloadableFiles(module: any) {
|
||||
let files = [];
|
||||
getContentDownloadableFiles(module: any): any[] {
|
||||
const files = [];
|
||||
|
||||
if (module.contents && module.contents.length) {
|
||||
module.contents.forEach((content) => {
|
||||
|
@ -220,11 +220,11 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref
|
|||
* @return {Promise<{size: number, total: boolean}>} Promise resolved with the size and a boolean indicating if it was able
|
||||
* to calculate the total size.
|
||||
*/
|
||||
getDownloadSize(module: any, courseId: number, single?: boolean) : Promise<{size: number, total: boolean}> {
|
||||
getDownloadSize(module: any, courseId: number, single?: boolean): Promise<{ size: number, total: boolean }> {
|
||||
return this.getFiles(module, courseId).then((files) => {
|
||||
return this.utils.sumFileSizes(files);
|
||||
}).catch(() => {
|
||||
return {size: -1, total: false};
|
||||
return { size: -1, total: false };
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -235,8 +235,9 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref
|
|||
* @param {number} courseId Course ID the module belongs to.
|
||||
* @return {number|Promise<number>} Size, or promise resolved with the size.
|
||||
*/
|
||||
getDownloadedSize?(module: any, courseId: number) : number|Promise<number> {
|
||||
getDownloadedSize?(module: any, courseId: number): number | Promise<number> {
|
||||
const siteId = this.sitesProvider.getCurrentSiteId();
|
||||
|
||||
return this.filepoolProvider.getFilesSizeByComponent(siteId, this.component, module.id);
|
||||
}
|
||||
|
||||
|
@ -248,7 +249,7 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref
|
|||
* @param {boolean} [single] True if we're downloading a single module, false if we're downloading a whole section.
|
||||
* @return {Promise<any[]>} Promise resolved with the list of files.
|
||||
*/
|
||||
getFiles(module: any, courseId: number, single?: boolean) : Promise<any[]> {
|
||||
getFiles(module: any, courseId: number, single?: boolean): Promise<any[]> {
|
||||
// Load module contents if needed.
|
||||
return this.loadContents(module, courseId).then(() => {
|
||||
return this.getIntroFiles(module, courseId).then((files) => {
|
||||
|
@ -264,7 +265,7 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref
|
|||
* @param {number} courseId Course ID.
|
||||
* @return {Promise<any[]>} Promise resolved with list of intro files.
|
||||
*/
|
||||
getIntroFiles(module: any, courseId: number) : Promise<any[]> {
|
||||
getIntroFiles(module: any, courseId: number): Promise<any[]> {
|
||||
return Promise.resolve(this.getIntroFilesFromInstance(module));
|
||||
}
|
||||
|
||||
|
@ -275,7 +276,7 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref
|
|||
* @param {any} [instance] The instance to get the intro files (book, assign, ...). If not defined, module will be used.
|
||||
* @return {any[]} List of intro files.
|
||||
*/
|
||||
getIntroFilesFromInstance(module: any, instance?: any) {
|
||||
getIntroFilesFromInstance(module: any, instance?: any): any[] {
|
||||
if (instance) {
|
||||
if (typeof instance.introfiles != 'undefined') {
|
||||
return instance.introfiles;
|
||||
|
@ -298,13 +299,14 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref
|
|||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Promise of the current download.
|
||||
*/
|
||||
getOngoingDownload(id: number, siteId?: string) : Promise<any> {
|
||||
getOngoingDownload(id: number, siteId?: string): Promise<any> {
|
||||
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||
|
||||
if (this.isDownloading(id, siteId)) {
|
||||
// There's already a download ongoing, return the promise.
|
||||
return this.downloadPromises[siteId][this.getUniqueId(id)];
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
|
@ -314,7 +316,7 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref
|
|||
* @param {number} id Unique ID inside component.
|
||||
* @return {string} Unique ID.
|
||||
*/
|
||||
getUniqueId(id: number) {
|
||||
getUniqueId(id: number): string {
|
||||
return this.component + '#' + id;
|
||||
}
|
||||
|
||||
|
@ -324,7 +326,7 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref
|
|||
* @param {number} moduleId The module ID.
|
||||
* @return {Promise<any>} Promise resolved when the data is invalidated.
|
||||
*/
|
||||
invalidateContent(moduleId: number) : Promise<any> {
|
||||
invalidateContent(moduleId: number): Promise<any> {
|
||||
const promises = [],
|
||||
siteId = this.sitesProvider.getCurrentSiteId();
|
||||
|
||||
|
@ -342,7 +344,7 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref
|
|||
* @param {number} courseId Course ID the module belongs to.
|
||||
* @return {Promise<any>} Promise resolved when invalidated.
|
||||
*/
|
||||
invalidateModule(module: any, courseId: number) : Promise<any> {
|
||||
invalidateModule(module: any, courseId: number): Promise<any> {
|
||||
return this.courseProvider.invalidateModule(module.id);
|
||||
}
|
||||
|
||||
|
@ -353,7 +355,7 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref
|
|||
* @param {number} courseId Course ID the module belongs to.
|
||||
* @return {boolean|Promise<boolean>} Whether the module can be downloaded. The promise should never be rejected.
|
||||
*/
|
||||
isDownloadable(module: any, courseId: number) : boolean|Promise<boolean> {
|
||||
isDownloadable(module: any, courseId: number): boolean | Promise<boolean> {
|
||||
// By default, mark all instances as downloadable.
|
||||
return true;
|
||||
}
|
||||
|
@ -361,12 +363,13 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref
|
|||
/**
|
||||
* Check if a there's an ongoing download for the given identifier.
|
||||
*
|
||||
* @param {number} id Unique identifier per component.
|
||||
* @param {number} id Unique identifier per component.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Boolean} True if downloading, false otherwise.
|
||||
* @return {boolean} True if downloading, false otherwise.
|
||||
*/
|
||||
isDownloading(id: number, siteId?: string) : boolean {
|
||||
isDownloading(id: number, siteId?: string): boolean {
|
||||
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||
|
||||
return !!(this.downloadPromises[siteId] && this.downloadPromises[siteId][this.getUniqueId(id)]);
|
||||
}
|
||||
|
||||
|
@ -375,7 +378,7 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref
|
|||
*
|
||||
* @return {boolean|Promise<boolean>} A boolean, or a promise resolved with a boolean, indicating if the handler is enabled.
|
||||
*/
|
||||
isEnabled() : boolean|Promise<boolean> {
|
||||
isEnabled(): boolean | Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -385,7 +388,7 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref
|
|||
* @param {any} file File to check.
|
||||
* @return {boolean} Whether the file is downloadable.
|
||||
*/
|
||||
isFileDownloadable(file: any) : boolean {
|
||||
isFileDownloadable(file: any): boolean {
|
||||
return file.type === 'file';
|
||||
}
|
||||
|
||||
|
@ -397,10 +400,11 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref
|
|||
* @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down).
|
||||
* @return {Promise} Promise resolved when loaded.
|
||||
*/
|
||||
loadContents(module: any, courseId: number, ignoreCache?: boolean) : Promise<void> {
|
||||
loadContents(module: any, courseId: number, ignoreCache?: boolean): Promise<void> {
|
||||
if (this.isResource) {
|
||||
return this.courseProvider.loadModuleContents(module, courseId, undefined, false, ignoreCache);
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
|
@ -432,8 +436,8 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref
|
|||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Promise resolved when the module has been downloaded. Data returned is not reliable.
|
||||
*/
|
||||
prefetchPackage(module: any, courseId: number, single: boolean, downloadFn: prefetchFunction, siteId?: string, ...args) :
|
||||
Promise<any> {
|
||||
prefetchPackage(module: any, courseId: number, single: boolean, downloadFn: prefetchFunction, siteId?: string, ...args: any[])
|
||||
: Promise<any> {
|
||||
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||
|
||||
if (!this.appProvider.isOnline()) {
|
||||
|
@ -469,8 +473,9 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref
|
|||
* @param {string} [extra] Extra data to store.
|
||||
* @return {Promise<any>} Promise resolved when done.
|
||||
*/
|
||||
setDownloaded(id: number, siteId?: string, extra?: string) : Promise<any> {
|
||||
setDownloaded(id: number, siteId?: string, extra?: string): Promise<any> {
|
||||
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||
|
||||
return this.filepoolProvider.storePackageStatus(siteId, CoreConstants.DOWNLOADED, this.component, id, extra);
|
||||
}
|
||||
|
||||
|
@ -481,8 +486,9 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref
|
|||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Promise resolved when done.
|
||||
*/
|
||||
setDownloading(id: number, siteId?: string) : Promise<any> {
|
||||
setDownloading(id: number, siteId?: string): Promise<any> {
|
||||
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||
|
||||
return this.filepoolProvider.storePackageStatus(siteId, CoreConstants.DOWNLOADING, this.component, id);
|
||||
}
|
||||
|
||||
|
@ -494,8 +500,9 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref
|
|||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<never>} Rejected promise.
|
||||
*/
|
||||
setPreviousStatusAndReject(id: number, error?: any, siteId?: string) : Promise<never> {
|
||||
setPreviousStatusAndReject(id: number, error?: any, siteId?: string): Promise<never> {
|
||||
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||
|
||||
return this.filepoolProvider.setPackagePreviousStatus(siteId, this.component, id).then(() => {
|
||||
return Promise.reject(error);
|
||||
});
|
||||
|
@ -508,7 +515,7 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref
|
|||
* @param {number} courseId Course ID the module belongs to.
|
||||
* @return {Promise<any>} Promise resolved when done.
|
||||
*/
|
||||
removeFiles(module: any, courseId: number) : Promise<any> {
|
||||
removeFiles(module: any, courseId: number): Promise<any> {
|
||||
return this.filepoolProvider.removeFilesByComponent(this.sitesProvider.getCurrentSiteId(), this.component, module.id);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,8 +12,10 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component, Input, OnInit, OnChanges, OnDestroy, ViewContainerRef, ComponentFactoryResolver, ViewChild, ChangeDetectorRef,
|
||||
SimpleChange, Output, EventEmitter } from '@angular/core';
|
||||
import {
|
||||
Component, Input, OnInit, OnChanges, OnDestroy, ViewContainerRef, ComponentFactoryResolver, ViewChild, ChangeDetectorRef,
|
||||
SimpleChange, Output, EventEmitter
|
||||
} from '@angular/core';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { CoreEventsProvider } from '../../../../providers/events';
|
||||
import { CoreLoggerProvider } from '../../../../providers/logger';
|
||||
|
@ -54,23 +56,23 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
|||
// The component hasn't been initialized yet. Store the container.
|
||||
this.componentContainers['courseFormat'] = el;
|
||||
}
|
||||
};
|
||||
}
|
||||
@ViewChild('courseSummary', { read: ViewContainerRef }) set courseSummary(el: ViewContainerRef) {
|
||||
this.createComponent('courseSummary', this.cfDelegate.getCourseSummaryComponent(this.course), el);
|
||||
};
|
||||
}
|
||||
@ViewChild('sectionSelector', { read: ViewContainerRef }) set sectionSelector(el: ViewContainerRef) {
|
||||
this.createComponent('sectionSelector', this.cfDelegate.getSectionSelectorComponent(this.course), el);
|
||||
};
|
||||
}
|
||||
@ViewChild('singleSection', { read: ViewContainerRef }) set singleSection(el: ViewContainerRef) {
|
||||
this.createComponent('singleSection', this.cfDelegate.getSingleSectionComponent(this.course), el);
|
||||
};
|
||||
}
|
||||
@ViewChild('allSections', { read: ViewContainerRef }) set allSections(el: ViewContainerRef) {
|
||||
this.createComponent('allSections', this.cfDelegate.getAllSectionsComponent(this.course), el);
|
||||
};
|
||||
}
|
||||
|
||||
// Instances and containers of all the components that the handler could define.
|
||||
protected componentContainers: {[type: string]: ViewContainerRef} = {};
|
||||
componentInstances: {[type: string]: any} = {};
|
||||
protected componentContainers: { [type: string]: ViewContainerRef } = {};
|
||||
componentInstances: { [type: string]: any } = {};
|
||||
|
||||
displaySectionSelector: boolean;
|
||||
selectedSection: any;
|
||||
|
@ -94,10 +96,10 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
|||
// Listen for section status changes.
|
||||
this.sectionStatusObserver = eventsProvider.on(CoreEventsProvider.SECTION_STATUS_CHANGED, (data) => {
|
||||
if (this.downloadEnabled && this.sections && this.sections.length && this.course && data.sectionId &&
|
||||
data.courseId == this.course.id) {
|
||||
// Check if the affected section is being downloaded. If so, we don't update section status
|
||||
// because it'll already be updated when the download finishes.
|
||||
let downloadId = this.courseHelper.getSectionDownloadId({id: data.sectionId});
|
||||
data.courseId == this.course.id) {
|
||||
// Check if the affected section is being downloaded.
|
||||
// If so, we don't update section status because it'll already be updated when the download finishes.
|
||||
const downloadId = this.courseHelper.getSectionDownloadId({ id: data.sectionId });
|
||||
if (prefetchDelegate.isBeingDownloaded(downloadId)) {
|
||||
return;
|
||||
}
|
||||
|
@ -105,7 +107,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
|||
// Get the affected section.
|
||||
let section;
|
||||
for (let i = 0; i < this.sections.length; i++) {
|
||||
let s = this.sections[i];
|
||||
const s = this.sections[i];
|
||||
if (s.id === data.sectionId) {
|
||||
section = s;
|
||||
break;
|
||||
|
@ -131,24 +133,24 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
|||
/**
|
||||
* Component being initialized.
|
||||
*/
|
||||
ngOnInit() {
|
||||
ngOnInit(): void {
|
||||
this.displaySectionSelector = this.cfDelegate.displaySectionSelector(this.course);
|
||||
|
||||
this.createComponent(
|
||||
'courseFormat', this.cfDelegate.getCourseFormatComponent(this.course), this.componentContainers['courseFormat']);
|
||||
'courseFormat', this.cfDelegate.getCourseFormatComponent(this.course), this.componentContainers['courseFormat']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect changes on input properties.
|
||||
*/
|
||||
ngOnChanges(changes: {[name: string]: SimpleChange}) {
|
||||
ngOnChanges(changes: { [name: string]: SimpleChange }): void {
|
||||
if (changes.sections && this.sections) {
|
||||
if (!this.selectedSection) {
|
||||
// There is no selected section yet, calculate which one to load.
|
||||
if (this.initialSectionId || this.initialSectionNumber) {
|
||||
// We have an input indicating the section ID to load. Search the section.
|
||||
for (let i = 0; i < this.sections.length; i++) {
|
||||
let section = this.sections[i];
|
||||
const section = this.sections[i];
|
||||
if (section.id == this.initialSectionId || section.section == this.initialSectionNumber) {
|
||||
this.loaded = true;
|
||||
this.sectionChanged(section);
|
||||
|
@ -166,7 +168,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
|||
// We have a selected section, but the list has changed. Search the section in the list.
|
||||
let newSection;
|
||||
for (let i = 0; i < this.sections.length; i++) {
|
||||
let section = this.sections[i];
|
||||
const section = this.sections[i];
|
||||
if (this.compareSections(section, this.selectedSection)) {
|
||||
newSection = section;
|
||||
break;
|
||||
|
@ -186,10 +188,10 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
|||
}
|
||||
|
||||
// Apply the changes to the components and call ngOnChanges if it exists.
|
||||
for (let type in this.componentInstances) {
|
||||
let instance = this.componentInstances[type];
|
||||
for (const type in this.componentInstances) {
|
||||
const instance = this.componentInstances[type];
|
||||
|
||||
for (let name in changes) {
|
||||
for (const name in changes) {
|
||||
instance[name] = changes[name].currentValue;
|
||||
}
|
||||
|
||||
|
@ -207,7 +209,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
|||
* @param {ViewContainerRef} container The container to add the component to.
|
||||
* @return {boolean} Whether the component was successfully created.
|
||||
*/
|
||||
protected createComponent(type: string, componentClass: any, container: ViewContainerRef) : boolean {
|
||||
protected createComponent(type: string, componentClass: any, container: ViewContainerRef): boolean {
|
||||
if (!componentClass || !container) {
|
||||
// No component to instantiate or container doesn't exist right now.
|
||||
return false;
|
||||
|
@ -236,8 +238,9 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
|||
this.cdr.detectChanges(); // The instances are used in ngIf, tell Angular that something has changed.
|
||||
|
||||
return true;
|
||||
} catch(ex) {
|
||||
} catch (ex) {
|
||||
this.logger.error('Error creating component', type, ex);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -247,8 +250,8 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
|||
*
|
||||
* @param {any} newSection The new selected section.
|
||||
*/
|
||||
sectionChanged(newSection: any) {
|
||||
let previousValue = this.selectedSection;
|
||||
sectionChanged(newSection: any): void {
|
||||
const previousValue = this.selectedSection;
|
||||
this.selectedSection = newSection;
|
||||
|
||||
// If there is a component to render the current section, update its section.
|
||||
|
@ -269,7 +272,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
|||
* @param {any} s2 Second section.
|
||||
* @return {boolean} Whether they're equal.
|
||||
*/
|
||||
compareSections(s1: any, s2: any) : boolean {
|
||||
compareSections(s1: any, s2: any): boolean {
|
||||
return s1 && s2 ? s1.id === s2.id : s1 === s2;
|
||||
}
|
||||
|
||||
|
@ -278,7 +281,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
|||
*
|
||||
* @param {boolean} refresh [description]
|
||||
*/
|
||||
protected calculateSectionsStatus(refresh?: boolean) : void {
|
||||
protected calculateSectionsStatus(refresh?: boolean): void {
|
||||
this.courseHelper.calculateSectionsStatus(this.sections, this.course.id, refresh).catch(() => {
|
||||
// Ignore errors (shouldn't happen).
|
||||
});
|
||||
|
@ -290,7 +293,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
|||
* @param {Event} e Click event.
|
||||
* @param {any} section Section to download.
|
||||
*/
|
||||
prefetch(e: Event, section: any) : void {
|
||||
prefetch(e: Event, section: any): void {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
|
@ -314,7 +317,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
|||
* @param {boolean} [manual] Whether the prefetch was started manually or it was automatically started because all modules
|
||||
* are being downloaded.
|
||||
*/
|
||||
protected prefetchSection(section: any, manual?: boolean) {
|
||||
protected prefetchSection(section: any, manual?: boolean): void {
|
||||
this.courseHelper.prefetchSection(section, this.course.id, this.sections).catch((error) => {
|
||||
// Don't show error message if it's an automatic download.
|
||||
if (!manual) {
|
||||
|
@ -328,7 +331,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
|||
/**
|
||||
* Component destroyed.
|
||||
*/
|
||||
ngOnDestroy() {
|
||||
ngOnDestroy(): void {
|
||||
if (this.sectionStatusObserver) {
|
||||
this.sectionStatusObserver.off();
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ import { TranslateService } from '@ngx-translate/core';
|
|||
import { CoreSitesProvider } from '../../../../providers/sites';
|
||||
import { CoreDomUtilsProvider } from '../../../../providers/utils/dom';
|
||||
import { CoreTextUtilsProvider } from '../../../../providers/utils/text';
|
||||
import { CoreUserProvider } from '../../../user/providers/user';
|
||||
|
||||
/**
|
||||
* Component to handle activity completion. It shows a checkbox with the current status, and allows manually changing
|
||||
|
@ -39,15 +40,15 @@ export class CoreCourseModuleCompletionComponent implements OnChanges {
|
|||
completionImage: string;
|
||||
completionDescription: string;
|
||||
|
||||
constructor(private textUtils: CoreTextUtilsProvider, private translate: TranslateService,
|
||||
private domUtils: CoreDomUtilsProvider, private sitesProvider: CoreSitesProvider) {
|
||||
constructor(private textUtils: CoreTextUtilsProvider, private domUtils: CoreDomUtilsProvider,
|
||||
private translate: TranslateService, private sitesProvider: CoreSitesProvider, private userProvider: CoreUserProvider) {
|
||||
this.completionChanged = new EventEmitter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect changes on input properties.
|
||||
*/
|
||||
ngOnChanges(changes: {[name: string]: SimpleChange}) {
|
||||
ngOnChanges(changes: { [name: string]: SimpleChange }): void {
|
||||
if (changes.completion && this.completion) {
|
||||
this.showStatus();
|
||||
}
|
||||
|
@ -58,7 +59,7 @@ export class CoreCourseModuleCompletionComponent implements OnChanges {
|
|||
*
|
||||
* @param {Event} e The click event.
|
||||
*/
|
||||
completionClicked(e: Event) : void {
|
||||
completionClicked(e: Event): void {
|
||||
if (this.completion) {
|
||||
if (typeof this.completion.cmid == 'undefined' || this.completion.tracking !== 1) {
|
||||
return;
|
||||
|
@ -67,7 +68,7 @@ export class CoreCourseModuleCompletionComponent implements OnChanges {
|
|||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
let modal = this.domUtils.showModalLoading(),
|
||||
const modal = this.domUtils.showModalLoading(),
|
||||
params = {
|
||||
cmid: this.completion.cmid,
|
||||
completed: this.completion.state === 1 ? 0 : 1
|
||||
|
@ -91,9 +92,9 @@ export class CoreCourseModuleCompletionComponent implements OnChanges {
|
|||
/**
|
||||
* Set image and description to show as completion icon.
|
||||
*/
|
||||
protected showStatus() : void {
|
||||
protected showStatus(): void {
|
||||
const moduleName = this.moduleName || '';
|
||||
let langKey,
|
||||
moduleName = this.moduleName || '',
|
||||
image;
|
||||
|
||||
if (this.completion.tracking === 1 && this.completion.state === 0) {
|
||||
|
@ -130,19 +131,19 @@ export class CoreCourseModuleCompletionComponent implements OnChanges {
|
|||
if (this.completion.overrideby > 0) {
|
||||
langKey += '-override';
|
||||
|
||||
// @todo: Get user profile.
|
||||
// promise = $mmUser.getProfile(scope.completion.overrideby, scope.completion.courseId, true).then(function(profile) {
|
||||
// return {
|
||||
// overrideuser: profile.fullname,
|
||||
// modname: modNameFormatted
|
||||
// };
|
||||
// });
|
||||
promise = this.userProvider.getProfile(this.completion.overrideby, this.completion.courseId, true).then(
|
||||
(profile) => {
|
||||
return {
|
||||
overrideuser: profile.fullname,
|
||||
modname: modNameFormatted
|
||||
};
|
||||
});
|
||||
} else {
|
||||
promise = Promise.resolve(modNameFormatted);
|
||||
}
|
||||
|
||||
return promise.then((translateParams) => {
|
||||
this.completionDescription = this.translate.instant(langKey, {$a: translateParams});
|
||||
this.completionDescription = this.translate.instant(langKey, { $a: translateParams });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -38,8 +38,10 @@ export class CoreCourseModuleDescriptionComponent {
|
|||
@Input() description: string; // The description to display.
|
||||
@Input() note?: string; // A note to display along with the description.
|
||||
@Input() component?: string; // Component for format text directive.
|
||||
@Input() componentId?: string|number; // Component ID to use in conjunction with the component.
|
||||
@Input() showFull?: string|boolean; // Whether to always display the full description.
|
||||
@Input() componentId?: string | number; // Component ID to use in conjunction with the component.
|
||||
@Input() showFull?: string | boolean; // Whether to always display the full description.
|
||||
|
||||
constructor() {}
|
||||
constructor() {
|
||||
// Nothing to do.
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ export class CoreCourseModuleComponent implements OnInit {
|
|||
/**
|
||||
* Component being initialized.
|
||||
*/
|
||||
ngOnInit() {
|
||||
ngOnInit(): void {
|
||||
// Handler data must be defined. If it isn't, set it to prevent errors.
|
||||
if (this.module && !this.module.handlerData) {
|
||||
this.module.handlerData = {};
|
||||
|
@ -51,7 +51,7 @@ export class CoreCourseModuleComponent implements OnInit {
|
|||
*
|
||||
* @param {Event} event Click event.
|
||||
*/
|
||||
moduleClicked(event: Event) {
|
||||
moduleClicked(event: Event): void {
|
||||
if (this.module.uservisible !== false && this.module.handlerData.action) {
|
||||
this.module.handlerData.action(event, this.navCtrl, this.module, this.courseId);
|
||||
}
|
||||
|
@ -63,7 +63,7 @@ export class CoreCourseModuleComponent implements OnInit {
|
|||
* @param {Event} event Click event.
|
||||
* @param {CoreCourseModuleHandlerButton} button The clicked button.
|
||||
*/
|
||||
buttonClicked(event: Event, button: CoreCourseModuleHandlerButton) {
|
||||
buttonClicked(event: Event, button: CoreCourseModuleHandlerButton): void {
|
||||
if (button && button.action) {
|
||||
button.action(event, this.navCtrl, this.module, this.courseId);
|
||||
}
|
||||
|
|
|
@ -13,9 +13,6 @@
|
|||
// limitations under the License.
|
||||
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { IonicPage, NavParams } from 'ionic-angular';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { CoreTextUtilsProvider } from '../../../../providers/utils/text';
|
||||
import { CoreCourseProvider } from '../../providers/course';
|
||||
import { CoreCourseModuleDelegate } from '../../providers/module-delegate';
|
||||
|
||||
|
@ -34,13 +31,12 @@ export class CoreCourseUnsupportedModuleComponent implements OnInit {
|
|||
isSupportedByTheApp: boolean;
|
||||
moduleName: string;
|
||||
|
||||
constructor(navParams: NavParams, private translate: TranslateService, private textUtils: CoreTextUtilsProvider,
|
||||
private courseProvider: CoreCourseProvider, private moduleDelegate: CoreCourseModuleDelegate) {}
|
||||
constructor(private courseProvider: CoreCourseProvider, private moduleDelegate: CoreCourseModuleDelegate) { }
|
||||
|
||||
/**
|
||||
* Component being initialized.
|
||||
*/
|
||||
ngOnInit() {
|
||||
ngOnInit(): void {
|
||||
this.isDisabledInSite = this.moduleDelegate.isModuleDisabledInSite(this.module.modname);
|
||||
this.isSupportedByTheApp = this.moduleDelegate.hasHandler(this.module.modname);
|
||||
this.moduleName = this.courseProvider.translateModuleName(this.module.modname);
|
||||
|
|
|
@ -18,10 +18,11 @@ import { CoreCourseHelperProvider } from './providers/helper';
|
|||
import { CoreCourseFormatDelegate } from './providers/format-delegate';
|
||||
import { CoreCourseModuleDelegate } from './providers/module-delegate';
|
||||
import { CoreCourseModulePrefetchDelegate } from './providers/module-prefetch-delegate';
|
||||
import { CoreCourseOptionsDelegate } from './providers/options-delegate';
|
||||
import { CoreCourseFormatDefaultHandler } from './providers/default-format';
|
||||
import { CoreCourseFormatSingleActivityModule } from './formats/singleactivity/singleactivity.module';
|
||||
import { CoreCourseFormatSocialModule } from './formats/social/social.module';
|
||||
import { CoreCourseFormatTopicsModule} from './formats/topics/topics.module';
|
||||
import { CoreCourseFormatTopicsModule } from './formats/topics/topics.module';
|
||||
import { CoreCourseFormatWeeksModule } from './formats/weeks/weeks.module';
|
||||
|
||||
@NgModule({
|
||||
|
@ -38,6 +39,7 @@ import { CoreCourseFormatWeeksModule } from './formats/weeks/weeks.module';
|
|||
CoreCourseFormatDelegate,
|
||||
CoreCourseModuleDelegate,
|
||||
CoreCourseModulePrefetchDelegate,
|
||||
CoreCourseOptionsDelegate,
|
||||
CoreCourseFormatDefaultHandler
|
||||
],
|
||||
exports: []
|
||||
|
|
|
@ -12,8 +12,7 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component, Input, OnChanges, ViewContainerRef, ComponentFactoryResolver, ChangeDetectorRef,
|
||||
SimpleChange } from '@angular/core';
|
||||
import { Component, Input, OnChanges, ViewContainerRef, ComponentFactoryResolver, SimpleChange } from '@angular/core';
|
||||
import { CoreLoggerProvider } from '../../../../../providers/logger';
|
||||
import { CoreCourseModuleDelegate } from '../../../providers/module-delegate';
|
||||
import { CoreCourseUnsupportedModuleComponent } from '../../../components/unsupported-module/unsupported-module';
|
||||
|
@ -37,17 +36,17 @@ export class CoreCourseFormatSingleActivityComponent implements OnChanges {
|
|||
protected componentInstance: any;
|
||||
|
||||
constructor(logger: CoreLoggerProvider, private viewRef: ViewContainerRef, private factoryResolver: ComponentFactoryResolver,
|
||||
private cdr: ChangeDetectorRef, private moduleDelegate: CoreCourseModuleDelegate) {
|
||||
private moduleDelegate: CoreCourseModuleDelegate) {
|
||||
this.logger = logger.getInstance('CoreCourseFormatSingleActivityComponent');
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect changes on input properties.
|
||||
*/
|
||||
ngOnChanges(changes: {[name: string]: SimpleChange}) {
|
||||
ngOnChanges(changes: { [name: string]: SimpleChange }): void {
|
||||
if (this.course && this.sections && this.sections.length) {
|
||||
// In single activity the module should only have 1 section and 1 module. Get the module.
|
||||
let module = this.sections[0] && this.sections[0].modules && this.sections[0].modules[0];
|
||||
const module = this.sections[0] && this.sections[0].modules && this.sections[0].modules[0];
|
||||
if (module && !this.componentInstance) {
|
||||
// We haven't created the component yet. Create it now.
|
||||
this.createComponent(module);
|
||||
|
@ -55,11 +54,11 @@ export class CoreCourseFormatSingleActivityComponent implements OnChanges {
|
|||
|
||||
if (this.componentInstance && this.componentInstance.ngOnChanges) {
|
||||
// Call ngOnChanges of the component.
|
||||
let newChanges: {[name: string]: SimpleChange} = {};
|
||||
const newChanges: { [name: string]: SimpleChange } = {};
|
||||
|
||||
// Check if course has changed.
|
||||
if (changes.course) {
|
||||
newChanges.course = changes.course
|
||||
newChanges.course = changes.course;
|
||||
this.componentInstance.course = this.course;
|
||||
}
|
||||
|
||||
|
@ -69,7 +68,7 @@ export class CoreCourseFormatSingleActivityComponent implements OnChanges {
|
|||
currentValue: module,
|
||||
firstChange: changes.sections.firstChange,
|
||||
previousValue: this.module,
|
||||
isFirstChange: () => {
|
||||
isFirstChange: (): boolean => {
|
||||
return newChanges.module.firstChange;
|
||||
}
|
||||
};
|
||||
|
@ -90,8 +89,8 @@ export class CoreCourseFormatSingleActivityComponent implements OnChanges {
|
|||
* @param {any} module The module.
|
||||
* @return {boolean} Whether the component was successfully created.
|
||||
*/
|
||||
protected createComponent(module: any) : boolean {
|
||||
let componentClass = this.moduleDelegate.getMainComponent(this.course, module) || CoreCourseUnsupportedModuleComponent;
|
||||
protected createComponent(module: any): boolean {
|
||||
const componentClass = this.moduleDelegate.getMainComponent(this.course, module) || CoreCourseUnsupportedModuleComponent;
|
||||
if (!componentClass) {
|
||||
// No component to instantiate.
|
||||
return false;
|
||||
|
@ -108,11 +107,10 @@ export class CoreCourseFormatSingleActivityComponent implements OnChanges {
|
|||
this.componentInstance.courseId = this.course.id;
|
||||
this.componentInstance.module = module;
|
||||
|
||||
// this.cdr.detectChanges(); // The instances are used in ngIf, tell Angular that something has changed.
|
||||
|
||||
return true;
|
||||
} catch(ex) {
|
||||
} catch (ex) {
|
||||
this.logger.error('Error creating component', ex);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,14 +23,16 @@ import { CoreCourseFormatSingleActivityComponent } from '../components/format';
|
|||
export class CoreCourseFormatSingleActivityHandler implements CoreCourseFormatHandler {
|
||||
name = 'singleactivity';
|
||||
|
||||
constructor() {}
|
||||
constructor() {
|
||||
// Nothing to do.
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not the handler is enabled on a site level.
|
||||
*
|
||||
* @return {boolean|Promise<boolean>} True or promise resolved with true if enabled.
|
||||
*/
|
||||
isEnabled() : boolean|Promise<boolean> {
|
||||
isEnabled(): boolean | Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -40,7 +42,7 @@ export class CoreCourseFormatSingleActivityHandler implements CoreCourseFormatHa
|
|||
* @param {any} course The course to check.
|
||||
* @type {boolean} Whether it can view all sections.
|
||||
*/
|
||||
canViewAllSections(course: any) : boolean {
|
||||
canViewAllSections(course: any): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -52,10 +54,11 @@ export class CoreCourseFormatSingleActivityHandler implements CoreCourseFormatHa
|
|||
* @param {any[]} [sections] List of sections.
|
||||
* @return {string} Title.
|
||||
*/
|
||||
getCourseTitle(course: any, sections?: any[]) : string {
|
||||
getCourseTitle(course: any, sections?: any[]): string {
|
||||
if (sections && sections[0] && sections[0].modules && sections[0].modules[0]) {
|
||||
return sections[0].modules[0].name;
|
||||
}
|
||||
|
||||
return course.fullname || '';
|
||||
}
|
||||
|
||||
|
@ -65,7 +68,7 @@ export class CoreCourseFormatSingleActivityHandler implements CoreCourseFormatHa
|
|||
* @param {any} course The course to check.
|
||||
* @type {boolean} Whether the default section selector should be displayed.
|
||||
*/
|
||||
displaySectionSelector(course: any) : boolean {
|
||||
displaySectionSelector(course: any): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -77,7 +80,7 @@ export class CoreCourseFormatSingleActivityHandler implements CoreCourseFormatHa
|
|||
* @param {any} course The course to render.
|
||||
* @return {any} The component to use, undefined if not found.
|
||||
*/
|
||||
getCourseFormatComponent(course: any) : any {
|
||||
getCourseFormatComponent(course: any): any {
|
||||
return CoreCourseFormatSingleActivityComponent;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,14 +22,16 @@ import { CoreCourseFormatHandler } from '../../../providers/format-delegate';
|
|||
export class CoreCourseFormatTopicsHandler implements CoreCourseFormatHandler {
|
||||
name = 'topics';
|
||||
|
||||
constructor() {}
|
||||
constructor() {
|
||||
// Nothing to do.
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not the handler is enabled on a site level.
|
||||
*
|
||||
* @return {boolean|Promise<boolean>} True or promise resolved with true if enabled.
|
||||
*/
|
||||
isEnabled() : boolean|Promise<boolean> {
|
||||
isEnabled(): boolean | Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,14 +24,14 @@ import { CoreConstants } from '../../../../constants';
|
|||
export class CoreCourseFormatWeeksHandler implements CoreCourseFormatHandler {
|
||||
name = 'weeks';
|
||||
|
||||
constructor(private timeUtils: CoreTimeUtilsProvider) {}
|
||||
constructor(private timeUtils: CoreTimeUtilsProvider) { }
|
||||
|
||||
/**
|
||||
* Whether or not the handler is enabled on a site level.
|
||||
*
|
||||
* @return {boolean|Promise<boolean>} True or promise resolved with true if enabled.
|
||||
*/
|
||||
isEnabled() : boolean|Promise<boolean> {
|
||||
isEnabled(): boolean | Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -42,8 +42,8 @@ export class CoreCourseFormatWeeksHandler implements CoreCourseFormatHandler {
|
|||
* @param {any[]} sections List of sections.
|
||||
* @return {any|Promise<any>} Current section (or promise resolved with current section).
|
||||
*/
|
||||
getCurrentSection(course: any, sections: any[]) : any|Promise<any> {
|
||||
let now = this.timeUtils.timestamp();
|
||||
getCurrentSection(course: any, sections: any[]): any | Promise<any> {
|
||||
const now = this.timeUtils.timestamp();
|
||||
|
||||
if (now < course.startdate || (course.enddate && now > course.enddate)) {
|
||||
// Course hasn't started yet or it has ended already. Return the first section.
|
||||
|
@ -51,12 +51,12 @@ export class CoreCourseFormatWeeksHandler implements CoreCourseFormatHandler {
|
|||
}
|
||||
|
||||
for (let i = 0; i < sections.length; i++) {
|
||||
let section = sections[i];
|
||||
const section = sections[i];
|
||||
if (typeof section.section == 'undefined' || section.section < 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let dates = this.getSectionDates(section, course.startdate);
|
||||
const dates = this.getSectionDates(section, course.startdate);
|
||||
if ((now >= dates.start) && (now < dates.end)) {
|
||||
return section;
|
||||
}
|
||||
|
@ -73,15 +73,16 @@ export class CoreCourseFormatWeeksHandler implements CoreCourseFormatHandler {
|
|||
* @param {number} startDate The course start date (in seconds).
|
||||
* @return {{start: number, end: number}} An object with the start and end date of the section.
|
||||
*/
|
||||
protected getSectionDates(section: any, startDate: number) : {start: number, end: number} {
|
||||
protected getSectionDates(section: any, startDate: number): { start: number, end: number } {
|
||||
// Hack alert. We add 2 hours to avoid possible DST problems. (e.g. we go into daylight savings and the date changes).
|
||||
startDate = startDate + 7200;
|
||||
|
||||
let dates = {
|
||||
const dates = {
|
||||
start: startDate + (CoreConstants.SECONDS_WEEK * (section.section - 1)),
|
||||
end: 0
|
||||
};
|
||||
dates.end = dates.start + CoreConstants.SECONDS_WEEK;
|
||||
|
||||
return dates;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,13 +22,14 @@ import { CoreTextUtilsProvider } from '../../../../providers/utils/text';
|
|||
import { CoreCourseProvider } from '../../providers/course';
|
||||
import { CoreCourseHelperProvider } from '../../providers/helper';
|
||||
import { CoreCourseFormatDelegate } from '../../providers/format-delegate';
|
||||
import { CoreCoursesDelegate, CoreCoursesHandlerToDisplay } from '../../../courses/providers/delegate';
|
||||
import { CoreCourseModulePrefetchDelegate } from '../../providers/module-prefetch-delegate';
|
||||
import { CoreCourseOptionsDelegate, CoreCourseOptionsHandlerToDisplay } from '../../providers/options-delegate';
|
||||
import { CoreCoursesProvider } from '../../../courses/providers/courses';
|
||||
|
||||
/**
|
||||
* Page that displays the list of courses the user is enrolled in.
|
||||
*/
|
||||
@IonicPage({segment: 'core-course-section'})
|
||||
@IonicPage({ segment: 'core-course-section' })
|
||||
@Component({
|
||||
selector: 'page-core-course-section',
|
||||
templateUrl: 'section.html',
|
||||
|
@ -41,10 +42,10 @@ export class CoreCourseSectionPage implements OnDestroy {
|
|||
sections: any[];
|
||||
sectionId: number;
|
||||
sectionNumber: number;
|
||||
courseHandlers: CoreCoursesHandlerToDisplay[];
|
||||
courseHandlers: CoreCourseOptionsHandlerToDisplay[];
|
||||
dataLoaded: boolean;
|
||||
downloadEnabled: boolean;
|
||||
downloadEnabledIcon: string = 'square-outline'; // Disabled by default.
|
||||
downloadEnabledIcon = 'square-outline'; // Disabled by default.
|
||||
prefetchCourseData = {
|
||||
prefetchCourseIcon: 'spinner'
|
||||
};
|
||||
|
@ -54,10 +55,11 @@ export class CoreCourseSectionPage implements OnDestroy {
|
|||
protected isDestroyed = false;
|
||||
|
||||
constructor(private navParams: NavParams, private courseProvider: CoreCourseProvider, private domUtils: CoreDomUtilsProvider,
|
||||
private courseFormatDelegate: CoreCourseFormatDelegate, private coursesDelegate: CoreCoursesDelegate,
|
||||
private courseFormatDelegate: CoreCourseFormatDelegate, private courseOptionsDelegate: CoreCourseOptionsDelegate,
|
||||
private translate: TranslateService, private courseHelper: CoreCourseHelperProvider, eventsProvider: CoreEventsProvider,
|
||||
private textUtils: CoreTextUtilsProvider, private coursesProvider: CoreCoursesProvider,
|
||||
sitesProvider: CoreSitesProvider, private navCtrl: NavController) {
|
||||
sitesProvider: CoreSitesProvider, private navCtrl: NavController,
|
||||
private prefetchDelegate: CoreCourseModulePrefetchDelegate) {
|
||||
this.course = navParams.get('course');
|
||||
this.sectionId = navParams.get('sectionId');
|
||||
this.sectionNumber = navParams.get('sectionNumber');
|
||||
|
@ -82,9 +84,9 @@ export class CoreCourseSectionPage implements OnDestroy {
|
|||
/**
|
||||
* View loaded.
|
||||
*/
|
||||
ionViewDidLoad() {
|
||||
ionViewDidLoad(): void {
|
||||
|
||||
let module = this.navParams.get('module');
|
||||
const module = this.navParams.get('module');
|
||||
if (module) {
|
||||
this.courseHelper.openModule(this.navCtrl, module, this.course.id, this.sectionId);
|
||||
}
|
||||
|
@ -118,11 +120,11 @@ export class CoreCourseSectionPage implements OnDestroy {
|
|||
/**
|
||||
* Fetch and load all the data required for the view.
|
||||
*/
|
||||
protected loadData(refresh?: boolean) {
|
||||
protected loadData(refresh?: boolean): Promise<any> {
|
||||
// First of all, get the course because the data might have changed.
|
||||
return this.coursesProvider.getUserCourse(this.course.id).then((course) => {
|
||||
let promises = [],
|
||||
promise;
|
||||
const promises = [];
|
||||
let promise;
|
||||
|
||||
this.course = course;
|
||||
|
||||
|
@ -148,10 +150,10 @@ export class CoreCourseSectionPage implements OnDestroy {
|
|||
section.formattedName = name;
|
||||
});
|
||||
section.hasContent = this.courseHelper.sectionHasContent(section);
|
||||
|
||||
return section;
|
||||
});
|
||||
|
||||
|
||||
if (this.courseFormatDelegate.canViewAllSections(this.course)) {
|
||||
// Add a fake first section (all sections).
|
||||
this.sections.unshift({
|
||||
|
@ -166,7 +168,7 @@ export class CoreCourseSectionPage implements OnDestroy {
|
|||
}));
|
||||
|
||||
// Load the course handlers.
|
||||
promises.push(this.coursesDelegate.getHandlersToDisplay(this.course, refresh, false).then((handlers) => {
|
||||
promises.push(this.courseOptionsDelegate.getHandlersToDisplay(this.course, refresh, false).then((handlers) => {
|
||||
this.courseHandlers = handlers;
|
||||
}));
|
||||
|
||||
|
@ -181,7 +183,7 @@ export class CoreCourseSectionPage implements OnDestroy {
|
|||
*
|
||||
* @param {any} refresher Refresher.
|
||||
*/
|
||||
doRefresh(refresher: any) {
|
||||
doRefresh(refresher: any): void {
|
||||
this.invalidateData().finally(() => {
|
||||
this.loadData(true).finally(() => {
|
||||
refresher.complete();
|
||||
|
@ -192,7 +194,7 @@ export class CoreCourseSectionPage implements OnDestroy {
|
|||
/**
|
||||
* The completion of any of the modules have changed.
|
||||
*/
|
||||
onCompletionChange() {
|
||||
onCompletionChange(): void {
|
||||
this.invalidateData().finally(() => {
|
||||
this.refreshAfterCompletionChange();
|
||||
});
|
||||
|
@ -201,16 +203,16 @@ export class CoreCourseSectionPage implements OnDestroy {
|
|||
/**
|
||||
* Invalidate the data.
|
||||
*/
|
||||
protected invalidateData() {
|
||||
let promises = [];
|
||||
protected invalidateData(): Promise<any> {
|
||||
const promises = [];
|
||||
|
||||
promises.push(this.courseProvider.invalidateSections(this.course.id));
|
||||
promises.push(this.coursesProvider.invalidateUserCourses());
|
||||
promises.push(this.courseFormatDelegate.invalidateData(this.course, this.sections));
|
||||
|
||||
// if ($scope.sections) {
|
||||
// promises.push($mmCoursePrefetchDelegate.invalidateCourseUpdates(courseId));
|
||||
// }
|
||||
if (this.sections) {
|
||||
promises.push(this.prefetchDelegate.invalidateCourseUpdates(this.course.id));
|
||||
}
|
||||
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
@ -218,9 +220,9 @@ export class CoreCourseSectionPage implements OnDestroy {
|
|||
/**
|
||||
* Refresh list after a completion change since there could be new activities.
|
||||
*/
|
||||
protected refreshAfterCompletionChange() {
|
||||
protected refreshAfterCompletionChange(): void {
|
||||
// Save scroll position to restore it once done.
|
||||
let scrollElement = this.content.getScrollElement(),
|
||||
const scrollElement = this.content.getScrollElement(),
|
||||
scrollTop = scrollElement.scrollTop || 0,
|
||||
scrollLeft = scrollElement.scrollLeft || 0;
|
||||
|
||||
|
@ -235,8 +237,10 @@ export class CoreCourseSectionPage implements OnDestroy {
|
|||
|
||||
/**
|
||||
* Determines the prefetch icon of the course.
|
||||
*
|
||||
* @return {Promise<void>} Promise resolved when done.
|
||||
*/
|
||||
protected determineCoursePrefetchIcon() {
|
||||
protected determineCoursePrefetchIcon(): Promise<void> {
|
||||
return this.courseHelper.getCourseStatusIcon(this.course.id).then((icon) => {
|
||||
this.prefetchCourseData.prefetchCourseIcon = icon;
|
||||
});
|
||||
|
@ -245,26 +249,26 @@ export class CoreCourseSectionPage implements OnDestroy {
|
|||
/**
|
||||
* Prefetch the whole course.
|
||||
*/
|
||||
prefetchCourse() {
|
||||
prefetchCourse(): void {
|
||||
this.courseHelper.confirmAndPrefetchCourse(this.prefetchCourseData, this.course, this.sections, this.courseHandlers)
|
||||
.then((downloaded) => {
|
||||
if (downloaded && this.downloadEnabled) {
|
||||
// Recalculate the status.
|
||||
this.courseHelper.calculateSectionsStatus(this.sections, this.course.id).catch(() => {
|
||||
// Ignore errors (shouldn't happen).
|
||||
});
|
||||
}
|
||||
}).catch((error) => {
|
||||
if (!this.isDestroyed) {
|
||||
this.domUtils.showErrorModalDefault(error, 'core.course.errordownloadingcourse', true);
|
||||
}
|
||||
});
|
||||
.then((downloaded) => {
|
||||
if (downloaded && this.downloadEnabled) {
|
||||
// Recalculate the status.
|
||||
this.courseHelper.calculateSectionsStatus(this.sections, this.course.id).catch(() => {
|
||||
// Ignore errors (shouldn't happen).
|
||||
});
|
||||
}
|
||||
}).catch((error) => {
|
||||
if (!this.isDestroyed) {
|
||||
this.domUtils.showErrorModalDefault(error, 'core.course.errordownloadingcourse', true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle download enabled.
|
||||
*/
|
||||
toggleDownload() {
|
||||
toggleDownload(): void {
|
||||
this.downloadEnabled = !this.downloadEnabled;
|
||||
this.downloadEnabledIcon = this.downloadEnabled ? 'checkbox-outline' : 'square-outline';
|
||||
}
|
||||
|
@ -272,7 +276,7 @@ export class CoreCourseSectionPage implements OnDestroy {
|
|||
/**
|
||||
* Page destroyed.
|
||||
*/
|
||||
ngOnDestroy() {
|
||||
ngOnDestroy(): void {
|
||||
this.isDestroyed = true;
|
||||
if (this.completionObserver) {
|
||||
this.completionObserver.off();
|
||||
|
|
|
@ -13,14 +13,14 @@
|
|||
// limitations under the License.
|
||||
|
||||
import { Component } from '@angular/core';
|
||||
import { IonicPage, NavParams, NavController } from 'ionic-angular';
|
||||
import { IonicPage, NavParams } from 'ionic-angular';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { CoreTextUtilsProvider } from '../../../../providers/utils/text';
|
||||
|
||||
/**
|
||||
* Page that displays info about an unsupported module.
|
||||
*/
|
||||
@IonicPage({segment: 'core-course-unsupported-module'})
|
||||
@IonicPage({ segment: 'core-course-unsupported-module' })
|
||||
@Component({
|
||||
selector: 'page-core-course-unsupported-module',
|
||||
templateUrl: 'unsupported-module.html',
|
||||
|
@ -28,15 +28,14 @@ import { CoreTextUtilsProvider } from '../../../../providers/utils/text';
|
|||
export class CoreCourseUnsupportedModulePage {
|
||||
module: any;
|
||||
|
||||
constructor(navParams: NavParams, private translate: TranslateService, private textUtils: CoreTextUtilsProvider,
|
||||
private navCtrl: NavController) {
|
||||
constructor(navParams: NavParams, private translate: TranslateService, private textUtils: CoreTextUtilsProvider) {
|
||||
this.module = navParams.get('module') || {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand the description.
|
||||
*/
|
||||
expandDescription() {
|
||||
expandDescription(): void {
|
||||
this.textUtils.expandText(this.translate.instant('core.description'), this.module.description);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,7 +27,10 @@ import { CoreConstants } from '../../constants';
|
|||
*/
|
||||
@Injectable()
|
||||
export class CoreCourseProvider {
|
||||
public static ALL_SECTIONS_ID = -1;
|
||||
static ALL_SECTIONS_ID = -1;
|
||||
static ACCESS_GUEST = 'courses_access_guest';
|
||||
static ACCESS_DEFAULT = 'courses_access_default';
|
||||
|
||||
protected ROOT_CACHE_KEY = 'mmCourse:';
|
||||
|
||||
// Variables for database.
|
||||
|
@ -85,10 +88,10 @@ export class CoreCourseProvider {
|
|||
* @param {number} courseId Course ID.
|
||||
* @param {any} completion Completion status of the module.
|
||||
*/
|
||||
checkModuleCompletion(courseId: number, completion: any) : void {
|
||||
checkModuleCompletion(courseId: number, completion: any): void {
|
||||
if (completion && completion.tracking === 2 && completion.state === 0) {
|
||||
this.invalidateSections(courseId).finally(() => {
|
||||
this.eventsProvider.trigger(CoreEventsProvider.COMPLETION_MODULE_VIEWED, {courseId: courseId});
|
||||
this.eventsProvider.trigger(CoreEventsProvider.COMPLETION_MODULE_VIEWED, { courseId: courseId });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -99,7 +102,7 @@ export class CoreCourseProvider {
|
|||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<void>} Promise resolved when all status are cleared.
|
||||
*/
|
||||
clearAllCoursesStatus(siteId?: string) : Promise<void> {
|
||||
clearAllCoursesStatus(siteId?: string): Promise<void> {
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
this.logger.debug('Clear all course status for site ' + site.id);
|
||||
|
||||
|
@ -117,13 +120,13 @@ export class CoreCourseProvider {
|
|||
* @param {number} [userId] User ID. If not defined, current user.
|
||||
* @return {Promise<any>} Promise resolved with the completion statuses: object where the key is module ID.
|
||||
*/
|
||||
getActivitiesCompletionStatus(courseId: number, siteId?: string, userId?: number) : Promise<any> {
|
||||
getActivitiesCompletionStatus(courseId: number, siteId?: string, userId?: number): Promise<any> {
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
userId = userId || site.getUserId();
|
||||
|
||||
this.logger.debug(`Getting completion status for user ${userId} in course ${courseId}`);
|
||||
|
||||
let params = {
|
||||
const params = {
|
||||
courseid: courseId,
|
||||
userid: userId
|
||||
},
|
||||
|
@ -135,6 +138,7 @@ export class CoreCourseProvider {
|
|||
if (data && data.statuses) {
|
||||
return this.utils.arrayToObject(data.statuses, 'cmid');
|
||||
}
|
||||
|
||||
return Promise.reject(null);
|
||||
});
|
||||
});
|
||||
|
@ -147,7 +151,7 @@ export class CoreCourseProvider {
|
|||
* @param {number} userId User ID.
|
||||
* @return {string} Cache key.
|
||||
*/
|
||||
protected getActivitiesCompletionCacheKey(courseId: number, userId: number) : string {
|
||||
protected getActivitiesCompletionCacheKey(courseId: number, userId: number): string {
|
||||
return this.ROOT_CACHE_KEY + 'activitiescompletion:' + courseId + ':' + userId;
|
||||
}
|
||||
|
||||
|
@ -158,12 +162,13 @@ export class CoreCourseProvider {
|
|||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Promise resolved with the data.
|
||||
*/
|
||||
getCourseStatusData(courseId: number, siteId?: string) : Promise<any> {
|
||||
getCourseStatusData(courseId: number, siteId?: string): Promise<any> {
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
return site.getDb().getRecord(this.COURSE_STATUS_TABLE, {id: courseId}).then((entry) => {
|
||||
return site.getDb().getRecord(this.COURSE_STATUS_TABLE, { id: courseId }).then((entry) => {
|
||||
if (!entry) {
|
||||
return Promise.reject(null);
|
||||
}
|
||||
|
||||
return entry;
|
||||
});
|
||||
});
|
||||
|
@ -176,7 +181,7 @@ export class CoreCourseProvider {
|
|||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<string>} Promise resolved with the status.
|
||||
*/
|
||||
getCourseStatus(courseId: number, siteId?: string) : Promise<string> {
|
||||
getCourseStatus(courseId: number, siteId?: string): Promise<string> {
|
||||
return this.getCourseStatusData(courseId, siteId).then((entry) => {
|
||||
return entry.status || CoreConstants.NOT_DOWNLOADED;
|
||||
}).catch(() => {
|
||||
|
@ -196,7 +201,7 @@ export class CoreCourseProvider {
|
|||
* @return {Promise<any>} Promise resolved with the module.
|
||||
*/
|
||||
getModule(moduleId: number, courseId?: number, sectionId?: number, preferCache?: boolean, ignoreCache?: boolean,
|
||||
siteId?: string) : Promise<any> {
|
||||
siteId?: string): Promise<any> {
|
||||
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||
|
||||
let promise;
|
||||
|
@ -219,7 +224,7 @@ export class CoreCourseProvider {
|
|||
// We have courseId, we can use core_course_get_contents for compatibility.
|
||||
this.logger.debug(`Getting module ${moduleId} in course ${courseId}`);
|
||||
|
||||
let params = {
|
||||
const params = {
|
||||
courseid: courseId,
|
||||
options: [
|
||||
{
|
||||
|
@ -250,15 +255,17 @@ export class CoreCourseProvider {
|
|||
return this.getSections(courseId, false, false, preSets, siteId);
|
||||
}).then((sections) => {
|
||||
for (let i = 0; i < sections.length; i++) {
|
||||
let section = sections[i];
|
||||
const section = sections[i];
|
||||
for (let j = 0; j < section.modules.length; j++) {
|
||||
let module = section.modules[j];
|
||||
const module = section.modules[j];
|
||||
if (module.id == moduleId) {
|
||||
module.course = courseId;
|
||||
|
||||
return module;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.reject(null);
|
||||
});
|
||||
});
|
||||
|
@ -271,9 +278,9 @@ export class CoreCourseProvider {
|
|||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Promise resolved with the module's info.
|
||||
*/
|
||||
getModuleBasicInfo(moduleId: number, siteId?: string) : Promise<any> {
|
||||
getModuleBasicInfo(moduleId: number, siteId?: string): Promise<any> {
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
let params = {
|
||||
const params = {
|
||||
cmid: moduleId
|
||||
},
|
||||
preSets = {
|
||||
|
@ -281,11 +288,12 @@ export class CoreCourseProvider {
|
|||
};
|
||||
|
||||
return site.read('core_course_get_course_module', params, preSets).then((response) => {
|
||||
if (response.warnings && response.warnings.length) {
|
||||
if (response.warnings && response.warnings.length) {
|
||||
return Promise.reject(response.warnings[0]);
|
||||
} else if (response.cm) {
|
||||
return response.cm;
|
||||
}
|
||||
|
||||
return Promise.reject(null);
|
||||
});
|
||||
});
|
||||
|
@ -298,9 +306,9 @@ export class CoreCourseProvider {
|
|||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Promise resolved with the module's grade info.
|
||||
*/
|
||||
getModuleBasicGradeInfo(moduleId: number, siteId?: string) : Promise<any> {
|
||||
getModuleBasicGradeInfo(moduleId: number, siteId?: string): Promise<any> {
|
||||
return this.getModuleBasicInfo(moduleId, siteId).then((info) => {
|
||||
let grade = {
|
||||
const grade = {
|
||||
advancedgrading: info.advancedgrading || false,
|
||||
grade: info.grade || false,
|
||||
gradecat: info.gradecat || false,
|
||||
|
@ -312,6 +320,7 @@ export class CoreCourseProvider {
|
|||
if (grade.grade !== false || grade.advancedgrading !== false || grade.outcomes !== false) {
|
||||
return grade;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
@ -324,9 +333,9 @@ export class CoreCourseProvider {
|
|||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Promise resolved with the module's info.
|
||||
*/
|
||||
getModuleBasicInfoByInstance(id: number, module: string, siteId?: string) : Promise<any> {
|
||||
getModuleBasicInfoByInstance(id: number, module: string, siteId?: string): Promise<any> {
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
let params = {
|
||||
const params = {
|
||||
instance: id,
|
||||
module: module
|
||||
},
|
||||
|
@ -335,11 +344,12 @@ export class CoreCourseProvider {
|
|||
};
|
||||
|
||||
return site.read('core_course_get_course_module_by_instance', params, preSets).then((response) => {
|
||||
if (response.warnings && response.warnings.length) {
|
||||
if (response.warnings && response.warnings.length) {
|
||||
return Promise.reject(response.warnings[0]);
|
||||
} else if (response.cm) {
|
||||
return response.cm;
|
||||
}
|
||||
|
||||
return Promise.reject(null);
|
||||
});
|
||||
});
|
||||
|
@ -352,7 +362,7 @@ export class CoreCourseProvider {
|
|||
* @param {string} module Name of the module. E.g. 'glossary'.
|
||||
* @return {string} Cache key.
|
||||
*/
|
||||
protected getModuleBasicInfoByInstanceCacheKey(id: number, module: string) : string {
|
||||
protected getModuleBasicInfoByInstanceCacheKey(id: number, module: string): string {
|
||||
return this.ROOT_CACHE_KEY + 'moduleByInstance:' + module + ':' + id;
|
||||
}
|
||||
|
||||
|
@ -362,7 +372,7 @@ export class CoreCourseProvider {
|
|||
* @param {number} moduleId Module ID.
|
||||
* @return {string} Cache key.
|
||||
*/
|
||||
protected getModuleCacheKey(moduleId: number) : string {
|
||||
protected getModuleCacheKey(moduleId: number): string {
|
||||
return this.ROOT_CACHE_KEY + 'module:' + moduleId;
|
||||
}
|
||||
|
||||
|
@ -372,7 +382,7 @@ export class CoreCourseProvider {
|
|||
* @param {string} moduleName The module name.
|
||||
* @return {string} The IMG src.
|
||||
*/
|
||||
getModuleIconSrc(moduleName: string) : string {
|
||||
getModuleIconSrc(moduleName: string): string {
|
||||
if (this.CORE_MODULES.indexOf(moduleName) < 0) {
|
||||
moduleName = 'external-tool';
|
||||
}
|
||||
|
@ -387,7 +397,7 @@ export class CoreCourseProvider {
|
|||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<number>} Promise resolved with the section ID.
|
||||
*/
|
||||
getModuleSectionId(moduleId: number, siteId?: string) : Promise<number> {
|
||||
getModuleSectionId(moduleId: number, siteId?: string): Promise<number> {
|
||||
// Try to get the section using getModuleBasicInfo.
|
||||
return this.getModuleBasicInfo(moduleId, siteId).then((module) => {
|
||||
return module.section;
|
||||
|
@ -405,7 +415,7 @@ export class CoreCourseProvider {
|
|||
* @return {Promise<any>} Promise resolved with the section.
|
||||
*/
|
||||
getSection(courseId: number, sectionId?: number, excludeModules?: boolean, excludeContents?: boolean, siteId?: string)
|
||||
: Promise<any> {
|
||||
: Promise<any> {
|
||||
|
||||
if (sectionId < 0) {
|
||||
return Promise.reject('Invalid section ID');
|
||||
|
@ -433,30 +443,30 @@ export class CoreCourseProvider {
|
|||
* @return {Promise} The reject contains the error message, else contains the sections.
|
||||
*/
|
||||
getSections(courseId?: number, excludeModules?: boolean, excludeContents?: boolean, preSets?: CoreSiteWSPreSets,
|
||||
siteId?: string) : Promise<any[]> {
|
||||
siteId?: string): Promise<any[]> {
|
||||
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
preSets = preSets || {};
|
||||
preSets.cacheKey = this.getSectionsCacheKey(courseId);
|
||||
preSets.getCacheUsingCacheKey = true; // This is to make sure users don't lose offline access when updating.
|
||||
|
||||
let params = {
|
||||
courseid: courseId,
|
||||
options: [
|
||||
{
|
||||
name: 'excludemodules',
|
||||
value: excludeModules ? 1 : 0
|
||||
},
|
||||
{
|
||||
name: 'excludecontents',
|
||||
value: excludeContents ? 1 : 0
|
||||
}
|
||||
]
|
||||
};
|
||||
const params = {
|
||||
courseid: courseId,
|
||||
options: [
|
||||
{
|
||||
name: 'excludemodules',
|
||||
value: excludeModules ? 1 : 0
|
||||
},
|
||||
{
|
||||
name: 'excludecontents',
|
||||
value: excludeContents ? 1 : 0
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
return site.read('core_course_get_contents', params, preSets).then((sections) => {
|
||||
let siteHomeId = site.getSiteHomeId(),
|
||||
showSections = true;
|
||||
const siteHomeId = site.getSiteHomeId();
|
||||
let showSections = true;
|
||||
|
||||
if (courseId == siteHomeId) {
|
||||
showSections = site.getStoredConfig('numsections');
|
||||
|
@ -478,7 +488,7 @@ export class CoreCourseProvider {
|
|||
* @param {number} courseId Course ID.
|
||||
* @return {string} Cache key.
|
||||
*/
|
||||
protected getSectionsCacheKey(courseId) : string {
|
||||
protected getSectionsCacheKey(courseId: number): string {
|
||||
return this.ROOT_CACHE_KEY + 'sections:' + courseId;
|
||||
}
|
||||
|
||||
|
@ -488,8 +498,8 @@ export class CoreCourseProvider {
|
|||
* @param {any[]} sections Sections.
|
||||
* @return {any[]} Modules.
|
||||
*/
|
||||
getSectionsModules(sections: any[]) : any[] {
|
||||
if (!sections || !sections.length) {
|
||||
getSectionsModules(sections: any[]): any[] {
|
||||
if (!sections || !sections.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
|
@ -499,6 +509,7 @@ export class CoreCourseProvider {
|
|||
modules = modules.concat(section.modules);
|
||||
}
|
||||
});
|
||||
|
||||
return modules;
|
||||
}
|
||||
|
||||
|
@ -509,7 +520,7 @@ export class CoreCourseProvider {
|
|||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Promise resolved when the data is invalidated.
|
||||
*/
|
||||
invalidateModule(moduleId: number, siteId?: string) : Promise<any> {
|
||||
invalidateModule(moduleId: number, siteId?: string): Promise<any> {
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
return site.invalidateWsCacheForKey(this.getModuleCacheKey(moduleId));
|
||||
});
|
||||
|
@ -523,7 +534,7 @@ export class CoreCourseProvider {
|
|||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Promise resolved when the data is invalidated.
|
||||
*/
|
||||
invalidateModuleByInstance(id: number, module: string, siteId?: string) : Promise<any> {
|
||||
invalidateModuleByInstance(id: number, module: string, siteId?: string): Promise<any> {
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
return site.invalidateWsCacheForKey(this.getModuleBasicInfoByInstanceCacheKey(id, module));
|
||||
});
|
||||
|
@ -537,9 +548,9 @@ export class CoreCourseProvider {
|
|||
* @param {number} [userId] User ID. If not defined, current user.
|
||||
* @return {Promise<any>} Promise resolved when the data is invalidated.
|
||||
*/
|
||||
invalidateSections(courseId: number, siteId?: string, userId?: number) : Promise<any> {
|
||||
invalidateSections(courseId: number, siteId?: string, userId?: number): Promise<any> {
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
let promises = [],
|
||||
const promises = [],
|
||||
siteHomeId = site.getSiteHomeId();
|
||||
|
||||
userId = userId || site.getUserId();
|
||||
|
@ -549,6 +560,7 @@ export class CoreCourseProvider {
|
|||
if (courseId == siteHomeId) {
|
||||
promises.push(site.invalidateConfig());
|
||||
}
|
||||
|
||||
return Promise.all(promises);
|
||||
});
|
||||
}
|
||||
|
@ -565,7 +577,7 @@ export class CoreCourseProvider {
|
|||
* @return {Promise<void>} Promise resolved when loaded.
|
||||
*/
|
||||
loadModuleContents(module: any, courseId?: number, sectionId?: number, preferCache?: boolean, ignoreCache?: boolean,
|
||||
siteId?: string) : Promise<void> {
|
||||
siteId?: string): Promise<void> {
|
||||
if (!ignoreCache && module.contents && module.contents.length) {
|
||||
// Already loaded.
|
||||
return Promise.resolve();
|
||||
|
@ -584,10 +596,11 @@ export class CoreCourseProvider {
|
|||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<void>} Promise resolved when the WS call is successful.
|
||||
*/
|
||||
logView(courseId: number, sectionNumber?: number, siteId?: string) : Promise<void> {
|
||||
let params: any = {
|
||||
logView(courseId: number, sectionNumber?: number, siteId?: string): Promise<void> {
|
||||
const params: any = {
|
||||
courseid: courseId
|
||||
};
|
||||
|
||||
if (typeof sectionNumber != 'undefined') {
|
||||
params.sectionnumber = sectionNumber;
|
||||
}
|
||||
|
@ -597,7 +610,7 @@ export class CoreCourseProvider {
|
|||
if (!response.status) {
|
||||
return Promise.reject(null);
|
||||
}
|
||||
})
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -608,13 +621,13 @@ export class CoreCourseProvider {
|
|||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<string>} Promise resolved when the status is changed. Resolve param: new status.
|
||||
*/
|
||||
setCoursePreviousStatus(courseId: number, siteId?: string) : Promise<string> {
|
||||
setCoursePreviousStatus(courseId: number, siteId?: string): Promise<string> {
|
||||
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||
|
||||
this.logger.debug(`Set previous status for course ${courseId} in site ${siteId}`);
|
||||
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
let db = site.getDb(),
|
||||
const db = site.getDb(),
|
||||
newData: any = {};
|
||||
|
||||
// Get current stored data.
|
||||
|
@ -628,9 +641,10 @@ export class CoreCourseProvider {
|
|||
newData.downloadTime = entry.previousDownloadTime;
|
||||
}
|
||||
|
||||
return db.updateRecords(this.COURSE_STATUS_TABLE, newData, {id: courseId}).then(() => {
|
||||
return db.updateRecords(this.COURSE_STATUS_TABLE, newData, { id: courseId }).then(() => {
|
||||
// Success updating, trigger event.
|
||||
this.triggerCourseStatusChanged(courseId, newData.status, siteId);
|
||||
|
||||
return newData.status;
|
||||
});
|
||||
});
|
||||
|
@ -645,8 +659,8 @@ export class CoreCourseProvider {
|
|||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<void>} Promise resolved when the status is stored.
|
||||
*/
|
||||
setCourseStatus(courseId: number, status: string, siteId?: string) : Promise<void> {
|
||||
siteId = siteId || this.sitesProvider.getCurrentSiteId()
|
||||
setCourseStatus(courseId: number, status: string, siteId?: string): Promise<void> {
|
||||
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||
|
||||
this.logger.debug(`Set status '${status}' for course ${courseId} in site ${siteId}`);
|
||||
|
||||
|
@ -666,7 +680,7 @@ export class CoreCourseProvider {
|
|||
downloadTime = entry.downloadTime;
|
||||
previousDownloadTime = entry.previousDownloadTime;
|
||||
} else {
|
||||
// downloadTime will be updated, store current time as previous.
|
||||
// The downloadTime will be updated, store current time as previous.
|
||||
previousDownloadTime = entry.downloadTime;
|
||||
}
|
||||
|
||||
|
@ -676,7 +690,7 @@ export class CoreCourseProvider {
|
|||
}).then((previousStatus) => {
|
||||
if (previousStatus != status) {
|
||||
// Status has changed, update it.
|
||||
let data = {
|
||||
const data = {
|
||||
id: courseId,
|
||||
status: status,
|
||||
previous: previousStatus,
|
||||
|
@ -685,7 +699,7 @@ export class CoreCourseProvider {
|
|||
previousDownloadTime: previousDownloadTime
|
||||
};
|
||||
|
||||
return site.getDb().insertOrUpdateRecord(this.COURSE_STATUS_TABLE, data, {id: courseId});
|
||||
return site.getDb().insertOrUpdateRecord(this.COURSE_STATUS_TABLE, data, { id: courseId });
|
||||
}
|
||||
}).then(() => {
|
||||
// Success inserting, trigger event.
|
||||
|
@ -700,7 +714,7 @@ export class CoreCourseProvider {
|
|||
* @param {string} moduleName The module name.
|
||||
* @return {string} Translated name.
|
||||
*/
|
||||
translateModuleName(moduleName: string) : string {
|
||||
translateModuleName(moduleName: string): string {
|
||||
if (this.CORE_MODULES.indexOf(moduleName) < 0) {
|
||||
moduleName = 'external-tool';
|
||||
}
|
||||
|
@ -718,7 +732,7 @@ export class CoreCourseProvider {
|
|||
* @param {string} status New course status.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
*/
|
||||
protected triggerCourseStatusChanged(courseId: number, status: string, siteId?: string) : void {
|
||||
protected triggerCourseStatusChanged(courseId: number, status: string, siteId?: string): void {
|
||||
this.eventsProvider.trigger(CoreEventsProvider.COURSE_STATUS_CHANGED, {
|
||||
courseId: courseId,
|
||||
status: status
|
||||
|
|
|
@ -25,14 +25,14 @@ import { CoreCourseProvider } from './course';
|
|||
export class CoreCourseFormatDefaultHandler implements CoreCourseFormatHandler {
|
||||
name = 'default';
|
||||
|
||||
constructor(private coursesProvider: CoreCoursesProvider) {}
|
||||
constructor(private coursesProvider: CoreCoursesProvider) { }
|
||||
|
||||
/**
|
||||
* Whether or not the handler is enabled on a site level.
|
||||
*
|
||||
* @return {boolean|Promise<boolean>} True or promise resolved with true if enabled.
|
||||
*/
|
||||
isEnabled() : boolean|Promise<boolean> {
|
||||
isEnabled(): boolean | Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -42,7 +42,7 @@ export class CoreCourseFormatDefaultHandler implements CoreCourseFormatHandler {
|
|||
* @param {any} course The course.
|
||||
* @return {string} Title.
|
||||
*/
|
||||
getCourseTitle?(course: any) : string {
|
||||
getCourseTitle?(course: any): string {
|
||||
return course.fullname || '';
|
||||
}
|
||||
|
||||
|
@ -52,7 +52,7 @@ export class CoreCourseFormatDefaultHandler implements CoreCourseFormatHandler {
|
|||
* @param {any} course The course to check.
|
||||
* @type {boolean} Whether it can view all sections.
|
||||
*/
|
||||
canViewAllSections(course: any) : boolean {
|
||||
canViewAllSections(course: any): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -62,7 +62,7 @@ export class CoreCourseFormatDefaultHandler implements CoreCourseFormatHandler {
|
|||
* @param {any} course The course to check.
|
||||
* @type {boolean} Whether the default section selector should be displayed.
|
||||
*/
|
||||
displaySectionSelector(course: any) : boolean {
|
||||
displaySectionSelector(course: any): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -73,16 +73,16 @@ export class CoreCourseFormatDefaultHandler implements CoreCourseFormatHandler {
|
|||
* @param {any[]} sections List of sections.
|
||||
* @return {any|Promise<any>} Current section (or promise resolved with current section).
|
||||
*/
|
||||
getCurrentSection(course: any, sections: any[]) : any|Promise<any> {
|
||||
getCurrentSection(course: any, sections: any[]): any | Promise<any> {
|
||||
// We need the "marker" to determine the current section.
|
||||
return this.coursesProvider.getCoursesByField('id', course.id).catch(() => {
|
||||
// Ignore errors.
|
||||
}).then((courses) => {
|
||||
if (courses && courses[0]) {
|
||||
// Find the marked section.
|
||||
let course = courses[0];
|
||||
const course = courses[0];
|
||||
for (let i = 0; i < sections.length; i++) {
|
||||
let section = sections[i];
|
||||
const section = sections[i];
|
||||
if (section.section == course.marker) {
|
||||
return section;
|
||||
}
|
||||
|
@ -91,7 +91,7 @@ export class CoreCourseFormatDefaultHandler implements CoreCourseFormatHandler {
|
|||
|
||||
// Marked section not found or we couldn't retrieve the marker. Return the first section.
|
||||
for (let i = 0; i < sections.length; i++) {
|
||||
let section = sections[i];
|
||||
const section = sections[i];
|
||||
if (section.id != CoreCourseProvider.ALL_SECTIONS_ID) {
|
||||
return section;
|
||||
}
|
||||
|
@ -108,7 +108,7 @@ export class CoreCourseFormatDefaultHandler implements CoreCourseFormatHandler {
|
|||
* @param {any[]} sections List of sections.
|
||||
* @return {Promise<any>} Promise resolved when the data is invalidated.
|
||||
*/
|
||||
invalidateData(course: any, sections: any[]) : Promise<any> {
|
||||
invalidateData(course: any, sections: any[]): Promise<any> {
|
||||
return this.coursesProvider.invalidateCoursesByField('id', course.id);
|
||||
}
|
||||
|
||||
|
@ -122,7 +122,7 @@ export class CoreCourseFormatDefaultHandler implements CoreCourseFormatHandler {
|
|||
* @param {any} course The course to open. It should contain a "format" attribute.
|
||||
* @return {Promise<any>} Promise resolved when done.
|
||||
*/
|
||||
openCourse(navCtrl: NavController, course: any) : Promise<any> {
|
||||
return navCtrl.push('CoreCourseSectionPage', {course: course});
|
||||
openCourse(navCtrl: NavController, course: any): Promise<any> {
|
||||
return navCtrl.push('CoreCourseSectionPage', { course: course });
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,24 +19,12 @@ import { CoreLoggerProvider } from '../../../providers/logger';
|
|||
import { CoreSitesProvider } from '../../../providers/sites';
|
||||
import { CoreCourseProvider } from './course';
|
||||
import { CoreCourseFormatDefaultHandler } from './default-format';
|
||||
import { CoreDelegate, CoreDelegateHandler } from '../../../classes/delegate';
|
||||
|
||||
/**
|
||||
* Interface that all course format handlers must implement.
|
||||
*/
|
||||
export interface CoreCourseFormatHandler {
|
||||
/**
|
||||
* Name of the format. It should match the "format" returned in core_course_get_courses.
|
||||
* @type {string}
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* Whether or not the handler is enabled on a site level.
|
||||
*
|
||||
* @return {boolean|Promise<boolean>} True or promise resolved with true if enabled.
|
||||
*/
|
||||
isEnabled(): boolean|Promise<boolean>;
|
||||
|
||||
export interface CoreCourseFormatHandler extends CoreDelegateHandler {
|
||||
/**
|
||||
* Get the title to use in course page. If not defined, course fullname.
|
||||
* This function will be called without sections first, and then call it again when the sections are retrieved.
|
||||
|
@ -45,7 +33,7 @@ export interface CoreCourseFormatHandler {
|
|||
* @param {any[]} [sections] List of sections.
|
||||
* @return {string} Title.
|
||||
*/
|
||||
getCourseTitle?(course: any, sections?: any[]) : string;
|
||||
getCourseTitle?(course: any, sections?: any[]): string;
|
||||
|
||||
/**
|
||||
* Whether it allows seeing all sections at the same time. Defaults to true.
|
||||
|
@ -53,7 +41,7 @@ export interface CoreCourseFormatHandler {
|
|||
* @param {any} course The course to check.
|
||||
* @type {boolean} Whether it can view all sections.
|
||||
*/
|
||||
canViewAllSections?(course: any) : boolean;
|
||||
canViewAllSections?(course: any): boolean;
|
||||
|
||||
/**
|
||||
* Whether the default section selector should be displayed. Defaults to true.
|
||||
|
@ -61,7 +49,7 @@ export interface CoreCourseFormatHandler {
|
|||
* @param {any} course The course to check.
|
||||
* @type {boolean} Whether the default section selector should be displayed.
|
||||
*/
|
||||
displaySectionSelector?(course: any) : boolean;
|
||||
displaySectionSelector?(course: any): boolean;
|
||||
|
||||
/**
|
||||
* Given a list of sections, get the "current" section that should be displayed first. Defaults to first section.
|
||||
|
@ -71,7 +59,7 @@ export interface CoreCourseFormatHandler {
|
|||
* @return {any|Promise<any>} Current section (or promise resolved with current section). If a promise is returned, it should
|
||||
* never fail.
|
||||
*/
|
||||
getCurrentSection?(course: any, sections: any[]) : any|Promise<any>;
|
||||
getCurrentSection?(course: any, sections: any[]): any | Promise<any>;
|
||||
|
||||
/**
|
||||
* Open the page to display a course. If not defined, the page CoreCourseSectionPage will be opened.
|
||||
|
@ -83,7 +71,7 @@ export interface CoreCourseFormatHandler {
|
|||
* @param {any} course The course to open. It should contain a "format" attribute.
|
||||
* @return {Promise<any>} Promise resolved when done.
|
||||
*/
|
||||
openCourse?(navCtrl: NavController, course: any) : Promise<any>;
|
||||
openCourse?(navCtrl: NavController, course: any): Promise<any>;
|
||||
|
||||
/**
|
||||
* Return the Component to use to display the course format instead of using the default one.
|
||||
|
@ -93,7 +81,7 @@ export interface CoreCourseFormatHandler {
|
|||
* @param {any} course The course to render.
|
||||
* @return {any} The component to use, undefined if not found.
|
||||
*/
|
||||
getCourseFormatComponent?(course: any) : any;
|
||||
getCourseFormatComponent?(course: any): any;
|
||||
|
||||
/**
|
||||
* Return the Component to use to display the course summary inside the default course format.
|
||||
|
@ -135,26 +123,21 @@ export interface CoreCourseFormatHandler {
|
|||
* @param {any[]} sections List of sections.
|
||||
* @return {Promise<any>} Promise resolved when the data is invalidated.
|
||||
*/
|
||||
invalidateData?(course: any, sections: any[]) : Promise<any>;
|
||||
};
|
||||
invalidateData?(course: any, sections: any[]): Promise<any>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Service to interact with course formats. Provides the functions to register and interact with the addons.
|
||||
*/
|
||||
@Injectable()
|
||||
export class CoreCourseFormatDelegate {
|
||||
protected logger;
|
||||
protected handlers: {[s: string]: CoreCourseFormatHandler} = {}; // All registered handlers.
|
||||
protected enabledHandlers: {[s: string]: CoreCourseFormatHandler} = {}; // Handlers enabled for the current site.
|
||||
protected lastUpdateHandlersStart: number;
|
||||
export class CoreCourseFormatDelegate extends CoreDelegate {
|
||||
protected handlers: { [s: string]: CoreCourseFormatHandler } = {}; // All registered handlers.
|
||||
protected enabledHandlers: { [s: string]: CoreCourseFormatHandler } = {}; // Handlers enabled for the current site.
|
||||
protected featurePrefix = 'CoreCourseFormatHandler_';
|
||||
|
||||
constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, eventsProvider: CoreEventsProvider,
|
||||
private defaultHandler: CoreCourseFormatDefaultHandler) {
|
||||
this.logger = logger.getInstance('CoreCoursesCourseFormatDelegate');
|
||||
|
||||
eventsProvider.on(CoreEventsProvider.LOGIN, this.updateHandlers.bind(this));
|
||||
eventsProvider.on(CoreEventsProvider.SITE_UPDATED, this.updateHandlers.bind(this));
|
||||
eventsProvider.on(CoreEventsProvider.REMOTE_ADDONS_LOADED, this.updateHandlers.bind(this));
|
||||
constructor(loggerProvider: CoreLoggerProvider, protected sitesProvider: CoreSitesProvider, eventsProvider: CoreEventsProvider,
|
||||
protected defaultHandler: CoreCourseFormatDefaultHandler) {
|
||||
super('CoreCoursesCourseFormatDelegate', loggerProvider, sitesProvider, eventsProvider);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -163,7 +146,7 @@ export class CoreCourseFormatDelegate {
|
|||
* @param {any} course The course to check.
|
||||
* @return {boolean} Whether it allows seeing all sections at the same time.
|
||||
*/
|
||||
canViewAllSections(course: any) : boolean {
|
||||
canViewAllSections(course: any): boolean {
|
||||
return this.executeFunction(course.format, 'canViewAllSections', [course]);
|
||||
}
|
||||
|
||||
|
@ -173,7 +156,7 @@ export class CoreCourseFormatDelegate {
|
|||
* @param {any} course The course to check.
|
||||
* @return {boolean} Whether the section selector should be displayed.
|
||||
*/
|
||||
displaySectionSelector(course: any) : boolean {
|
||||
displaySectionSelector(course: any): boolean {
|
||||
return this.executeFunction(course.format, 'displaySectionSelector', [course]);
|
||||
}
|
||||
|
||||
|
@ -186,8 +169,8 @@ export class CoreCourseFormatDelegate {
|
|||
* @param {any[]} params Parameters to pass to the function.
|
||||
* @return {any} Function returned value or default value.
|
||||
*/
|
||||
protected executeFunction(format: string, fnName: string, params?: any[]) : any {
|
||||
let handler = this.enabledHandlers[format];
|
||||
protected executeFunction(format: string, fnName: string, params?: any[]): any {
|
||||
const handler = this.enabledHandlers[format];
|
||||
if (handler && handler[fnName]) {
|
||||
return handler[fnName].apply(handler, params);
|
||||
} else if (this.defaultHandler[fnName]) {
|
||||
|
@ -201,7 +184,7 @@ export class CoreCourseFormatDelegate {
|
|||
* @param {any} course The course to render.
|
||||
* @return {any} The component to use, undefined if not found.
|
||||
*/
|
||||
getAllSectionsComponent(course: any) : any {
|
||||
getAllSectionsComponent(course: any): any {
|
||||
return this.executeFunction(course.format, 'getAllSectionsComponent', [course]);
|
||||
}
|
||||
|
||||
|
@ -211,7 +194,7 @@ export class CoreCourseFormatDelegate {
|
|||
* @param {any} course The course to render.
|
||||
* @return {any} The component to use, undefined if not found.
|
||||
*/
|
||||
getCourseFormatComponent(course: any) : any {
|
||||
getCourseFormatComponent(course: any): any {
|
||||
return this.executeFunction(course.format, 'getCourseFormatComponent', [course]);
|
||||
}
|
||||
|
||||
|
@ -221,7 +204,7 @@ export class CoreCourseFormatDelegate {
|
|||
* @param {any} course The course to render.
|
||||
* @return {any} The component to use, undefined if not found.
|
||||
*/
|
||||
getCourseSummaryComponent(course: any) : any {
|
||||
getCourseSummaryComponent(course: any): any {
|
||||
return this.executeFunction(course.format, 'getCourseSummaryComponent', [course]);
|
||||
}
|
||||
|
||||
|
@ -232,7 +215,7 @@ export class CoreCourseFormatDelegate {
|
|||
* @param {any[]} [sections] List of sections.
|
||||
* @return {string} Course title.
|
||||
*/
|
||||
getCourseTitle(course: any, sections?: any[]) : string {
|
||||
getCourseTitle(course: any, sections?: any[]): string {
|
||||
return this.executeFunction(course.format, 'getCourseTitle', [course, sections]);
|
||||
}
|
||||
|
||||
|
@ -243,13 +226,14 @@ export class CoreCourseFormatDelegate {
|
|||
* @param {any[]} sections List of sections.
|
||||
* @return {Promise<any>} Promise resolved with current section.
|
||||
*/
|
||||
getCurrentSection(course: any, sections: any[]) : Promise<any> {
|
||||
getCurrentSection(course: any, sections: any[]): Promise<any> {
|
||||
// Convert the result to a Promise if it isn't.
|
||||
return Promise.resolve(this.executeFunction(course.format, 'getCurrentSection', [course, sections])).catch(() => {
|
||||
// This function should never fail. Just return the first section.
|
||||
if (sections[0].id != CoreCourseProvider.ALL_SECTIONS_ID) {
|
||||
return sections[0];
|
||||
}
|
||||
|
||||
return sections[1];
|
||||
});
|
||||
}
|
||||
|
@ -260,7 +244,7 @@ export class CoreCourseFormatDelegate {
|
|||
* @param {any} course The course to render.
|
||||
* @return {any} The component to use, undefined if not found.
|
||||
*/
|
||||
getSectionSelectorComponent(course: any) : any {
|
||||
getSectionSelectorComponent(course: any): any {
|
||||
return this.executeFunction(course.format, 'getSectionSelectorComponent', [course]);
|
||||
}
|
||||
|
||||
|
@ -271,7 +255,7 @@ export class CoreCourseFormatDelegate {
|
|||
* @param {any} course The course to render.
|
||||
* @return {any} The component to use, undefined if not found.
|
||||
*/
|
||||
getSingleSectionComponent(course: any) : any {
|
||||
getSingleSectionComponent(course: any): any {
|
||||
return this.executeFunction(course.format, 'getSingleSectionComponent', [course]);
|
||||
}
|
||||
|
||||
|
@ -282,24 +266,10 @@ export class CoreCourseFormatDelegate {
|
|||
* @param {any[]} sections List of sections.
|
||||
* @return {Promise<any>} Promise resolved when the data is invalidated.
|
||||
*/
|
||||
invalidateData(course: any, sections: any[]) : Promise<any> {
|
||||
invalidateData(course: any, sections: any[]): Promise<any> {
|
||||
return this.executeFunction(course.format, 'invalidateData', [course, sections]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a time belongs to the last update handlers call.
|
||||
* This is to handle the cases where updateHandlers don't finish in the same order as they're called.
|
||||
*
|
||||
* @param {number} time Time to check.
|
||||
* @return {boolean} Whether it's the last call.
|
||||
*/
|
||||
isLastUpdateCall(time: number) : boolean {
|
||||
if (!this.lastUpdateHandlersStart) {
|
||||
return true;
|
||||
}
|
||||
return time == this.lastUpdateHandlersStart;
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a course.
|
||||
*
|
||||
|
@ -307,85 +277,11 @@ export class CoreCourseFormatDelegate {
|
|||
* @param {any} course The course to open. It should contain a "format" attribute.
|
||||
* @return {Promise<any>} Promise resolved when done.
|
||||
*/
|
||||
openCourse(navCtrl: NavController, course: any) : Promise<any> {
|
||||
openCourse(navCtrl: NavController, course: any): Promise<any> {
|
||||
if (this.enabledHandlers[course.format] && this.enabledHandlers[course.format].openCourse) {
|
||||
return this.enabledHandlers[course.format].openCourse(navCtrl, course);
|
||||
}
|
||||
return navCtrl.push('CoreCourseSectionPage', {course: course});
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a handler.
|
||||
*
|
||||
* @param {CoreCourseFormatHandler} handler The handler to register.
|
||||
* @return {boolean} True if registered successfully, false otherwise.
|
||||
*/
|
||||
registerHandler(handler: CoreCourseFormatHandler) : boolean {
|
||||
if (typeof this.handlers[handler.name] !== 'undefined') {
|
||||
this.logger.log(`Addon '${handler.name}' already registered`);
|
||||
return false;
|
||||
}
|
||||
this.logger.log(`Registered addon '${handler.name}'`);
|
||||
this.handlers[handler.name] = handler;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the handler for the current site.
|
||||
*
|
||||
* @param {CoreCourseFormatHandler} handler The handler to check.
|
||||
* @param {number} time Time this update process started.
|
||||
* @return {Promise<void>} Resolved when done.
|
||||
*/
|
||||
protected updateHandler(handler: CoreCourseFormatHandler, time: number) : Promise<void> {
|
||||
let promise,
|
||||
siteId = this.sitesProvider.getCurrentSiteId(),
|
||||
currentSite = this.sitesProvider.getCurrentSite();
|
||||
|
||||
if (!this.sitesProvider.isLoggedIn()) {
|
||||
promise = Promise.reject(null);
|
||||
} else if (currentSite.isFeatureDisabled('CoreCourseFormatHandler_' + handler.name)) {
|
||||
promise = Promise.resolve(false);
|
||||
} else {
|
||||
promise = Promise.resolve(handler.isEnabled());
|
||||
}
|
||||
|
||||
// Checks if the handler is enabled.
|
||||
return promise.catch(() => {
|
||||
return false;
|
||||
}).then((enabled: boolean) => {
|
||||
// Verify that this call is the last one that was started.
|
||||
// Check that site hasn't changed since the check started.
|
||||
if (this.isLastUpdateCall(time) && this.sitesProvider.getCurrentSiteId() === siteId) {
|
||||
if (enabled) {
|
||||
this.enabledHandlers[handler.name] = handler;
|
||||
} else {
|
||||
delete this.enabledHandlers[handler.name];
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the handlers for the current site.
|
||||
*
|
||||
* @return {Promise<any>} Resolved when done.
|
||||
*/
|
||||
protected updateHandlers() : Promise<any> {
|
||||
let promises = [],
|
||||
now = Date.now();
|
||||
|
||||
this.logger.debug('Updating handlers for current site.');
|
||||
|
||||
this.lastUpdateHandlersStart = now;
|
||||
|
||||
// Loop over all the handlers.
|
||||
for (let name in this.handlers) {
|
||||
promises.push(this.updateHandler(this.handlers[name], now));
|
||||
}
|
||||
|
||||
return Promise.all(promises).catch(() => {
|
||||
// Never reject.
|
||||
});
|
||||
return navCtrl.push('CoreCourseSectionPage', { course: course });
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,11 +21,11 @@ import { CoreDomUtilsProvider } from '../../../providers/utils/dom';
|
|||
import { CoreTextUtilsProvider } from '../../../providers/utils/text';
|
||||
import { CoreTimeUtilsProvider } from '../../../providers/utils/time';
|
||||
import { CoreUtilsProvider } from '../../../providers/utils/utils';
|
||||
import { CoreCoursesDelegate, CoreCoursesHandlerToDisplay } from '../../courses/providers/delegate';
|
||||
import { CoreCourseOptionsDelegate, CoreCourseOptionsHandlerToDisplay } from './options-delegate';
|
||||
import { CoreSiteHomeProvider } from '../../sitehome/providers/sitehome';
|
||||
import { CoreCourseProvider } from './course';
|
||||
import { CoreCourseModuleDelegate } from './module-delegate';
|
||||
import { CoreCourseModulePrefetchDelegate, CoreCourseModulePrefetchHandler } from './module-prefetch-delegate';
|
||||
import { CoreCourseModulePrefetchDelegate } from './module-prefetch-delegate';
|
||||
import { CoreLoginHelperProvider } from '../../login/providers/helper';
|
||||
import { CoreConstants } from '../../constants';
|
||||
import { CoreSite } from '../../../classes/site';
|
||||
|
@ -107,14 +107,14 @@ export type CoreCourseCoursesProgress = {
|
|||
@Injectable()
|
||||
export class CoreCourseHelperProvider {
|
||||
|
||||
protected courseDwnPromises: {[s: string]: {[id: number]: Promise<any>}} = {};
|
||||
protected courseDwnPromises: { [s: string]: { [id: number]: Promise<any> } } = {};
|
||||
|
||||
constructor(private courseProvider: CoreCourseProvider, private domUtils: CoreDomUtilsProvider,
|
||||
private moduleDelegate: CoreCourseModuleDelegate, private prefetchDelegate: CoreCourseModulePrefetchDelegate,
|
||||
private filepoolProvider: CoreFilepoolProvider, private sitesProvider: CoreSitesProvider,
|
||||
private textUtils: CoreTextUtilsProvider, private timeUtils: CoreTimeUtilsProvider,
|
||||
private utils: CoreUtilsProvider, private translate: TranslateService, private coursesDelegate: CoreCoursesDelegate,
|
||||
private loginHelper: CoreLoginHelperProvider, private siteHomeProvider: CoreSiteHomeProvider) {}
|
||||
private moduleDelegate: CoreCourseModuleDelegate, private prefetchDelegate: CoreCourseModulePrefetchDelegate,
|
||||
private filepoolProvider: CoreFilepoolProvider, private sitesProvider: CoreSitesProvider,
|
||||
private textUtils: CoreTextUtilsProvider, private timeUtils: CoreTimeUtilsProvider,
|
||||
private utils: CoreUtilsProvider, private translate: TranslateService, private loginHelper: CoreLoginHelperProvider,
|
||||
private courseOptionsDelegate: CoreCourseOptionsDelegate, private siteHomeProvider: CoreSiteHomeProvider) { }
|
||||
|
||||
/**
|
||||
* This function treats every module on the sections provided to load the handler data, treat completion
|
||||
|
@ -125,7 +125,7 @@ export class CoreCourseHelperProvider {
|
|||
* @param {any[]} [completionStatus] List of completion status.
|
||||
* @return {boolean} Whether the sections have content.
|
||||
*/
|
||||
addHandlerDataForModules(sections: any[], courseId: number, completionStatus?: any) {
|
||||
addHandlerDataForModules(sections: any[], courseId: number, completionStatus?: any): boolean {
|
||||
let hasContent = false;
|
||||
|
||||
sections.forEach((section) => {
|
||||
|
@ -157,7 +157,7 @@ export class CoreCourseHelperProvider {
|
|||
* @param {boolean} [refresh] True if it shouldn't use module status cache (slower).
|
||||
* @return {Promise<any>} Promise resolved when the status is calculated.
|
||||
*/
|
||||
calculateSectionStatus(section: any, courseId: number, refresh?: boolean) : Promise<any> {
|
||||
calculateSectionStatus(section: any, courseId: number, refresh?: boolean): Promise<any> {
|
||||
|
||||
if (section.id == CoreCourseProvider.ALL_SECTIONS_ID) {
|
||||
return Promise.reject(null);
|
||||
|
@ -199,10 +199,10 @@ export class CoreCourseHelperProvider {
|
|||
* @param {boolean} [refresh] True if it shouldn't use module status cache (slower).
|
||||
* @return {Promise<void>} Promise resolved when the states are calculated.
|
||||
*/
|
||||
calculateSectionsStatus(sections: any[], courseId: number, refresh?: boolean) : Promise<void> {
|
||||
calculateSectionsStatus(sections: any[], courseId: number, refresh?: boolean): Promise<void> {
|
||||
const promises = [];
|
||||
let allSectionsSection,
|
||||
allSectionsStatus,
|
||||
promises = [];
|
||||
allSectionsStatus;
|
||||
|
||||
sections.forEach((section) => {
|
||||
if (section.id === CoreCourseProvider.ALL_SECTIONS_ID) {
|
||||
|
@ -242,15 +242,16 @@ export class CoreCourseHelperProvider {
|
|||
* @param {any} iconData An object where to store the course icon. It will be stored with the name "prefetchCourseIcon".
|
||||
* @param {any} course Course to prefetch.
|
||||
* @param {any[]} [sections] List of course sections.
|
||||
* @param {CoreCoursesHandlerToDisplay[]} courseHandlers List of course handlers.
|
||||
* @param {CoreCourseOptionsHandlerToDisplay[]} courseHandlers List of course handlers.
|
||||
* @return {Promise<boolean>} Promise resolved with true when the download finishes, resolved with false if user doesn't
|
||||
* confirm, rejected if an error occurs.
|
||||
*/
|
||||
confirmAndPrefetchCourse(iconData: any, course: any, sections?: any[], courseHandlers?: CoreCoursesHandlerToDisplay[])
|
||||
confirmAndPrefetchCourse(iconData: any, course: any, sections?: any[], courseHandlers?: CoreCourseOptionsHandlerToDisplay[])
|
||||
: Promise<boolean> {
|
||||
let initialIcon = iconData.prefetchCourseIcon,
|
||||
promise,
|
||||
|
||||
const initialIcon = iconData.prefetchCourseIcon,
|
||||
siteId = this.sitesProvider.getCurrentSiteId();
|
||||
let promise;
|
||||
|
||||
iconData.prefetchCourseIcon = 'spinner';
|
||||
|
||||
|
@ -268,26 +269,27 @@ export class CoreCourseHelperProvider {
|
|||
if (courseHandlers) {
|
||||
promise = Promise.resolve(courseHandlers);
|
||||
} else {
|
||||
promise = this.coursesDelegate.getHandlersToDisplay(course);
|
||||
promise = this.courseOptionsDelegate.getHandlersToDisplay(course);
|
||||
}
|
||||
|
||||
return promise.then((handlers: CoreCoursesHandlerToDisplay[]) => {
|
||||
return promise.then((handlers: CoreCourseOptionsHandlerToDisplay[]) => {
|
||||
// Now we have all the data, download the course.
|
||||
return this.prefetchCourse(course, sections, handlers, siteId);
|
||||
}).then(() => {
|
||||
// Download successful.
|
||||
return true;
|
||||
});
|
||||
}, (error) : any => {
|
||||
}, (error): any => {
|
||||
// User cancelled or there was an error calculating the size.
|
||||
iconData.prefetchCourseIcon = initialIcon;
|
||||
if (error) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirm and prefetches a list of courses.
|
||||
|
@ -296,18 +298,18 @@ export class CoreCourseHelperProvider {
|
|||
* @param {Function} [onProgress] Function to call everytime a course is downloaded.
|
||||
* @return {Promise<boolean>} Resolved with true when downloaded, resolved with false if user cancels, rejected if error.
|
||||
*/
|
||||
confirmAndPrefetchCourses(courses: any[], onProgress?: (data: CoreCourseCoursesProgress) => void) : Promise<boolean> {
|
||||
confirmAndPrefetchCourses(courses: any[], onProgress?: (data: CoreCourseCoursesProgress) => void): Promise<boolean> {
|
||||
const siteId = this.sitesProvider.getCurrentSiteId();
|
||||
|
||||
// Confirm the download without checking size because it could take a while.
|
||||
return this.domUtils.showConfirm(this.translate.instant('core.areyousure')).then(() => {
|
||||
let promises = [],
|
||||
total = courses.length,
|
||||
count = 0;
|
||||
const promises = [],
|
||||
total = courses.length;
|
||||
let count = 0;
|
||||
|
||||
courses.forEach((course) => {
|
||||
let subPromises = [],
|
||||
sections,
|
||||
const subPromises = [];
|
||||
let sections,
|
||||
handlers,
|
||||
success = true;
|
||||
|
||||
|
@ -315,7 +317,7 @@ export class CoreCourseHelperProvider {
|
|||
subPromises.push(this.courseProvider.getSections(course.id, false, true).then((courseSections) => {
|
||||
sections = courseSections;
|
||||
}));
|
||||
subPromises.push(this.coursesDelegate.getHandlersToDisplay(course).then((cHandlers) => {
|
||||
subPromises.push(this.courseOptionsDelegate.getHandlersToDisplay(course).then((cHandlers) => {
|
||||
handlers = cHandlers;
|
||||
}));
|
||||
|
||||
|
@ -323,19 +325,20 @@ export class CoreCourseHelperProvider {
|
|||
return this.prefetchCourse(course, sections, handlers, siteId);
|
||||
}).catch((error) => {
|
||||
success = false;
|
||||
|
||||
return Promise.reject(error);
|
||||
}).finally(() => {
|
||||
// Course downloaded or failed, notify the progress.
|
||||
count++;
|
||||
if (onProgress) {
|
||||
onProgress({count: count, total: total, courseId: course.id, success: success});
|
||||
onProgress({ count: count, total: total, courseId: course.id, success: success });
|
||||
}
|
||||
}));
|
||||
});
|
||||
|
||||
if (onProgress) {
|
||||
// Notify the start of the download.
|
||||
onProgress({count: 0, total: total, success: true});
|
||||
onProgress({ count: 0, total: total, success: true });
|
||||
}
|
||||
|
||||
return this.utils.allPromises(promises).then(() => {
|
||||
|
@ -345,7 +348,7 @@ export class CoreCourseHelperProvider {
|
|||
// User cancelled.
|
||||
return false;
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Show confirmation dialog and then remove a module files.
|
||||
|
@ -354,7 +357,7 @@ export class CoreCourseHelperProvider {
|
|||
* @param {number} courseId Course ID the module belongs to.
|
||||
* @return {Promise<any>} Promise resolved when done.
|
||||
*/
|
||||
confirmAndRemoveFiles(module: any, courseId: number) : Promise<any> {
|
||||
confirmAndRemoveFiles(module: any, courseId: number): Promise<any> {
|
||||
return this.domUtils.showConfirm(this.translate.instant('course.confirmdeletemodulefiles')).then(() => {
|
||||
return this.prefetchDelegate.removeModuleFiles(module, courseId);
|
||||
});
|
||||
|
@ -369,14 +372,14 @@ export class CoreCourseHelperProvider {
|
|||
* @param {boolean} [alwaysConfirm] True to show a confirm even if the size isn't high, false otherwise.
|
||||
* @return {Promise<any>} Promise resolved if the user confirms or there's no need to confirm.
|
||||
*/
|
||||
confirmDownloadSizeSection(courseId: number, section?: any, sections?: any[], alwaysConfirm?: boolean) : Promise<any> {
|
||||
confirmDownloadSizeSection(courseId: number, section?: any, sections?: any[], alwaysConfirm?: boolean): Promise<any> {
|
||||
let sizePromise;
|
||||
|
||||
// Calculate the size of the download.
|
||||
if (section && section.id != CoreCourseProvider.ALL_SECTIONS_ID) {
|
||||
sizePromise = this.prefetchDelegate.getDownloadSize(section.modules, courseId);
|
||||
} else {
|
||||
let promises = [],
|
||||
const promises = [],
|
||||
results = {
|
||||
size: 0,
|
||||
total: true
|
||||
|
@ -408,7 +411,7 @@ export class CoreCourseHelperProvider {
|
|||
* @param {any[]} courses Courses
|
||||
* @return {Promise<string>} Promise resolved with the status.
|
||||
*/
|
||||
determineCoursesStatus(courses: any[]) : Promise<string> {
|
||||
determineCoursesStatus(courses: any[]): Promise<string> {
|
||||
// Get the status of each course.
|
||||
const promises = [],
|
||||
siteId = this.sitesProvider.getCurrentSiteId();
|
||||
|
@ -423,6 +426,7 @@ export class CoreCourseHelperProvider {
|
|||
for (let i = 1; i < statuses.length; i++) {
|
||||
status = this.filepoolProvider.determinePackagesStatus(status, statuses[i]);
|
||||
}
|
||||
|
||||
return status;
|
||||
});
|
||||
}
|
||||
|
@ -434,8 +438,9 @@ export class CoreCourseHelperProvider {
|
|||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Download promise, undefined if not found.
|
||||
*/
|
||||
getCourseDownloadPromise(courseId: number, siteId?: string) : Promise<any> {
|
||||
getCourseDownloadPromise(courseId: number, siteId?: string): Promise<any> {
|
||||
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||
|
||||
return this.courseDwnPromises[siteId] && this.courseDwnPromises[siteId][courseId];
|
||||
}
|
||||
|
||||
|
@ -446,7 +451,7 @@ export class CoreCourseHelperProvider {
|
|||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<string>} Promise resolved with the icon name.
|
||||
*/
|
||||
getCourseStatusIcon(courseId: number, siteId?: string) : Promise<string> {
|
||||
getCourseStatusIcon(courseId: number, siteId?: string): Promise<string> {
|
||||
return this.courseProvider.getCourseStatus(courseId, siteId).then((status) => {
|
||||
return this.getCourseStatusIconFromStatus(status);
|
||||
});
|
||||
|
@ -461,7 +466,7 @@ export class CoreCourseHelperProvider {
|
|||
* @param {String} status Course status.
|
||||
* @return {String} Icon name.
|
||||
*/
|
||||
getCourseStatusIconFromStatus(status: string) : string {
|
||||
getCourseStatusIconFromStatus(status: string): string {
|
||||
if (status == CoreConstants.DOWNLOADED) {
|
||||
// Always show refresh icon, we cannot knew if there's anything new in course options.
|
||||
return 'refresh';
|
||||
|
@ -480,11 +485,12 @@ export class CoreCourseHelperProvider {
|
|||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<number>} Promise resolved with the module's course ID.
|
||||
*/
|
||||
getModuleCourseIdByInstance(id: number, module: any, siteId?: string) : Promise<number> {
|
||||
getModuleCourseIdByInstance(id: number, module: any, siteId?: string): Promise<number> {
|
||||
return this.courseProvider.getModuleBasicInfoByInstance(id, module, siteId).then((cm) => {
|
||||
return cm.course;
|
||||
}).catch((error) => {
|
||||
this.domUtils.showErrorModalDefault(error, 'core.course.errorgetmodule', true);
|
||||
|
||||
return Promise.reject(null);
|
||||
});
|
||||
}
|
||||
|
@ -500,7 +506,7 @@ export class CoreCourseHelperProvider {
|
|||
*/
|
||||
getModulePrefetchInfo(module: any, courseId: number, invalidateCache?: boolean, component?: string)
|
||||
: Promise<CoreCourseModulePrefetchInfo> {
|
||||
let moduleInfo: CoreCourseModulePrefetchInfo = {},
|
||||
const moduleInfo: CoreCourseModulePrefetchInfo = {},
|
||||
siteId = this.sitesProvider.getCurrentSiteId(),
|
||||
promises = [];
|
||||
|
||||
|
@ -514,19 +520,6 @@ export class CoreCourseHelperProvider {
|
|||
}));
|
||||
|
||||
// @todo: Decide what to display instead of timemodified. Last check_updates?
|
||||
// promises.push(this.prefetchDelegate.getModuleTimemodified(module, courseId).then(function(moduleModified) {
|
||||
// moduleInfo.timemodified = moduleModified;
|
||||
// if (moduleModified > 0) {
|
||||
// var now = $mmUtil.timestamp();
|
||||
// if (now - moduleModified < 7 * 86400) {
|
||||
// moduleInfo.timemodifiedReadable = moment(moduleModified * 1000).fromNow();
|
||||
// } else {
|
||||
// moduleInfo.timemodifiedReadable = moment(moduleModified * 1000).calendar();
|
||||
// }
|
||||
// } else {
|
||||
// moduleInfo.timemodifiedReadable = "";
|
||||
// }
|
||||
// }));
|
||||
|
||||
promises.push(this.prefetchDelegate.getModuleStatus(module, courseId).then((moduleStatus) => {
|
||||
moduleInfo.status = moduleStatus;
|
||||
|
@ -573,7 +566,7 @@ export class CoreCourseHelperProvider {
|
|||
* @param {any} section Section.
|
||||
* @return {string} Section download ID.
|
||||
*/
|
||||
getSectionDownloadId(section: any) : string {
|
||||
getSectionDownloadId(section: any): string {
|
||||
return 'Section-' + section.id;
|
||||
}
|
||||
|
||||
|
@ -586,11 +579,11 @@ export class CoreCourseHelperProvider {
|
|||
* @param {number} [sectionId] Section the module belongs to. If not defined we'll try to retrieve it from the site.
|
||||
* @return {Promise<void>} Promise resolved when done.
|
||||
*/
|
||||
navigateToModule(moduleId: number, siteId?: string, courseId?: number, sectionId?: number) : Promise<void> {
|
||||
navigateToModule(moduleId: number, siteId?: string, courseId?: number, sectionId?: number): Promise<void> {
|
||||
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||
|
||||
let modal = this.domUtils.showModalLoading(),
|
||||
promise,
|
||||
const modal = this.domUtils.showModalLoading();
|
||||
let promise,
|
||||
site: CoreSite;
|
||||
|
||||
if (courseId && sectionId) {
|
||||
|
@ -619,7 +612,7 @@ export class CoreCourseHelperProvider {
|
|||
return this.courseProvider.getModule(moduleId, courseId, sectionId, false, false, siteId);
|
||||
}).then((module) => {
|
||||
const params = {
|
||||
course: {id: courseId},
|
||||
course: { id: courseId },
|
||||
module: module,
|
||||
sectionId: sectionId
|
||||
};
|
||||
|
@ -649,12 +642,12 @@ export class CoreCourseHelperProvider {
|
|||
* @param {number} courseId The course ID of the module.
|
||||
* @param {number} [sectionId] The section ID of the module.
|
||||
*/
|
||||
openModule(navCtrl: NavController, module: any, courseId: number, sectionId?: number) : void {
|
||||
openModule(navCtrl: NavController, module: any, courseId: number, sectionId?: number): void {
|
||||
if (!module.handlerData) {
|
||||
module.handlerData = this.moduleDelegate.getModuleDataFor(module.modname, module, courseId, sectionId);
|
||||
}
|
||||
|
||||
module.handlerData.action(new Event('click'), navCtrl, module, courseId, {animate: false});
|
||||
module.handlerData.action(new Event('click'), navCtrl, module, courseId, { animate: false });
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -662,11 +655,12 @@ export class CoreCourseHelperProvider {
|
|||
*
|
||||
* @param {any} course The course to prefetch.
|
||||
* @param {any[]} sections List of course sections.
|
||||
* @param {CoreCoursesHandlerToDisplay[]} courseHandlers List of course handlers.
|
||||
* @param {CoreCourseOptionsHandlerToDisplay[]} courseHandlers List of course options handlers.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise} Promise resolved when the download finishes.
|
||||
*/
|
||||
prefetchCourse(course: any, sections: any[], courseHandlers: CoreCoursesHandlerToDisplay[], siteId?: string) : Promise<any> {
|
||||
prefetchCourse(course: any, sections: any[], courseHandlers: CoreCourseOptionsHandlerToDisplay[], siteId?: string)
|
||||
: Promise<any> {
|
||||
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||
|
||||
if (this.courseDwnPromises[siteId] && this.courseDwnPromises[siteId][course.id]) {
|
||||
|
@ -678,35 +672,35 @@ export class CoreCourseHelperProvider {
|
|||
|
||||
// First of all, mark the course as being downloaded.
|
||||
this.courseDwnPromises[siteId][course.id] = this.courseProvider.setCourseStatus(course.id, CoreConstants.DOWNLOADING,
|
||||
siteId).then(() => {
|
||||
let promises = [],
|
||||
allSectionsSection = sections[0];
|
||||
siteId).then(() => {
|
||||
const promises = [];
|
||||
let allSectionsSection = sections[0];
|
||||
|
||||
// Prefetch all the sections. If the first section is "All sections", use it. Otherwise, use a fake "All sections".
|
||||
if (sections[0].id != CoreCourseProvider.ALL_SECTIONS_ID) {
|
||||
allSectionsSection = {id: CoreCourseProvider.ALL_SECTIONS_ID};
|
||||
}
|
||||
promises.push(this.prefetchSection(allSectionsSection, course.id, sections));
|
||||
|
||||
// Prefetch course options.
|
||||
courseHandlers.forEach((handler) => {
|
||||
if (handler.prefetch) {
|
||||
promises.push(handler.prefetch(course));
|
||||
// Prefetch all the sections. If the first section is "All sections", use it. Otherwise, use a fake "All sections".
|
||||
if (sections[0].id != CoreCourseProvider.ALL_SECTIONS_ID) {
|
||||
allSectionsSection = { id: CoreCourseProvider.ALL_SECTIONS_ID };
|
||||
}
|
||||
});
|
||||
promises.push(this.prefetchSection(allSectionsSection, course.id, sections));
|
||||
|
||||
return this.utils.allPromises(promises);
|
||||
}).then(() => {
|
||||
// Download success, mark the course as downloaded.
|
||||
return this.courseProvider.setCourseStatus(course.id, CoreConstants.DOWNLOADED, siteId);
|
||||
}).catch((error) => {
|
||||
// Error, restore previous status.
|
||||
return this.courseProvider.setCoursePreviousStatus(course.id, siteId).then(() => {
|
||||
return Promise.reject(error);
|
||||
// Prefetch course options.
|
||||
courseHandlers.forEach((handler) => {
|
||||
if (handler.prefetch) {
|
||||
promises.push(handler.prefetch(course));
|
||||
}
|
||||
});
|
||||
|
||||
return this.utils.allPromises(promises);
|
||||
}).then(() => {
|
||||
// Download success, mark the course as downloaded.
|
||||
return this.courseProvider.setCourseStatus(course.id, CoreConstants.DOWNLOADED, siteId);
|
||||
}).catch((error) => {
|
||||
// Error, restore previous status.
|
||||
return this.courseProvider.setCoursePreviousStatus(course.id, siteId).then(() => {
|
||||
return Promise.reject(error);
|
||||
});
|
||||
}).finally(() => {
|
||||
delete this.courseDwnPromises[siteId][course.id];
|
||||
});
|
||||
}).finally(() => {
|
||||
delete this.courseDwnPromises[siteId][course.id];
|
||||
});
|
||||
|
||||
return this.courseDwnPromises[siteId][course.id];
|
||||
}
|
||||
|
@ -722,11 +716,12 @@ export class CoreCourseHelperProvider {
|
|||
* @param {boolean} [refresh] True if refreshing, false otherwise.
|
||||
* @return {Promise<any>} Promise resolved when downloaded.
|
||||
*/
|
||||
prefetchModule(handler: any, module: any, size: any, courseId: number, refresh?: boolean) : Promise<any> {
|
||||
prefetchModule(handler: any, module: any, size: any, courseId: number, refresh?: boolean): Promise<any> {
|
||||
// Show confirmation if needed.
|
||||
return this.domUtils.confirmDownloadSize(size).then(() => {
|
||||
// Invalidate content if refreshing and download the data.
|
||||
let promise = refresh ? handler.invalidateContent(module.id, courseId) : Promise.resolve();
|
||||
const promise = refresh ? handler.invalidateContent(module.id, courseId) : Promise.resolve();
|
||||
|
||||
return promise.catch(() => {
|
||||
// Ignore errors.
|
||||
}).then(() => {
|
||||
|
@ -744,7 +739,7 @@ export class CoreCourseHelperProvider {
|
|||
* @param {any[]} [sections] List of sections. Used when downloading all the sections.
|
||||
* @return {Promise<any>} Promise resolved when the prefetch is finished.
|
||||
*/
|
||||
prefetchSection(section: any, courseId: number, sections?: any[]) : Promise<any> {
|
||||
prefetchSection(section: any, courseId: number, sections?: any[]): Promise<any> {
|
||||
if (section.id != CoreCourseProvider.ALL_SECTIONS_ID) {
|
||||
// Download only this section.
|
||||
return this.prefetchSingleSectionIfNeeded(section, courseId).then(() => {
|
||||
|
@ -753,8 +748,8 @@ export class CoreCourseHelperProvider {
|
|||
});
|
||||
} else {
|
||||
// Download all the sections except "All sections".
|
||||
let promises = [],
|
||||
allSectionsStatus;
|
||||
const promises = [];
|
||||
let allSectionsStatus;
|
||||
|
||||
section.isDownloading = true;
|
||||
sections.forEach((section) => {
|
||||
|
@ -788,7 +783,7 @@ export class CoreCourseHelperProvider {
|
|||
* @param {number} courseId Course ID the section belongs to.
|
||||
* @return {Promise<any>} Promise resolved when the section is prefetched.
|
||||
*/
|
||||
protected prefetchSingleSectionIfNeeded(section: any, courseId: number) : Promise<any> {
|
||||
protected prefetchSingleSectionIfNeeded(section: any, courseId: number): Promise<any> {
|
||||
if (section.id == CoreCourseProvider.ALL_SECTIONS_ID) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
@ -801,9 +796,11 @@ export class CoreCourseHelperProvider {
|
|||
// Section is downloaded or not downloadable, nothing to do.
|
||||
return;
|
||||
}
|
||||
|
||||
return this.prefetchSingleSection(section, result, courseId);
|
||||
}, (error) => {
|
||||
section.isDownloading = false;
|
||||
|
||||
return Promise.reject(error);
|
||||
});
|
||||
}
|
||||
|
@ -817,7 +814,7 @@ export class CoreCourseHelperProvider {
|
|||
* @param {number} courseId Course ID the section belongs to.
|
||||
* @return {Promise<any>} Promise resolved when the section has been prefetched.
|
||||
*/
|
||||
protected prefetchSingleSection(section: any, result: any, courseId: number) {
|
||||
protected prefetchSingleSection(section: any, result: any, courseId: number): Promise<any> {
|
||||
if (section.id == CoreCourseProvider.ALL_SECTIONS_ID) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
@ -828,14 +825,13 @@ export class CoreCourseHelperProvider {
|
|||
}
|
||||
|
||||
// We only download modules with status notdownloaded, downloading or outdated.
|
||||
let modules = result[CoreConstants.OUTDATED].concat(result[CoreConstants.NOT_DOWNLOADED])
|
||||
.concat(result[CoreConstants.DOWNLOADING]),
|
||||
const modules = result[CoreConstants.OUTDATED].concat(result[CoreConstants.NOT_DOWNLOADED])
|
||||
.concat(result[CoreConstants.DOWNLOADING]),
|
||||
downloadId = this.getSectionDownloadId(section);
|
||||
|
||||
section.isDownloading = true;
|
||||
|
||||
// We prefetch all the modules to prevent incoeherences in the download count
|
||||
// and also to download stale data that might not be marked as outdated.
|
||||
// Prefetch all modules to prevent incoeherences in download count and to download stale data not marked as outdated.
|
||||
return this.prefetchDelegate.prefetchModules(downloadId, modules, courseId, (data) => {
|
||||
section.count = data.count;
|
||||
section.total = data.total;
|
||||
|
@ -848,12 +844,12 @@ export class CoreCourseHelperProvider {
|
|||
* @param {any} section Section to check.
|
||||
* @return {boolean} Whether the section has content.
|
||||
*/
|
||||
sectionHasContent(section: any) : boolean {
|
||||
sectionHasContent(section: any): boolean {
|
||||
if (section.id == CoreCourseProvider.ALL_SECTIONS_ID || section.hiddenbynumsections) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (typeof section.availabilityinfo != 'undefined' && section.availabilityinfo != '') ||
|
||||
section.summary != '' || (section.modules && section.modules.length > 0);
|
||||
section.summary != '' || (section.modules && section.modules.length > 0);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,30 +19,12 @@ import { CoreLoggerProvider } from '../../../providers/logger';
|
|||
import { CoreSitesProvider } from '../../../providers/sites';
|
||||
import { CoreCourseProvider } from './course';
|
||||
import { CoreSite } from '../../../classes/site';
|
||||
import { CoreDelegate, CoreDelegateHandler } from '../../../classes/delegate';
|
||||
|
||||
/**
|
||||
* Interface that all course module handlers must implement.
|
||||
*/
|
||||
export interface CoreCourseModuleHandler {
|
||||
/**
|
||||
* A name to identify the addon.
|
||||
* @type {string}
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* Name of the module. It should match the "modname" of the module returned in core_course_get_contents.
|
||||
* @type {string}
|
||||
*/
|
||||
modname: string;
|
||||
|
||||
/**
|
||||
* Whether or not the handler is enabled on a site level.
|
||||
*
|
||||
* @return {boolean|Promise<boolean>} True or promise resolved with true if enabled.
|
||||
*/
|
||||
isEnabled(): boolean|Promise<boolean>;
|
||||
|
||||
export interface CoreCourseModuleHandler extends CoreDelegateHandler {
|
||||
/**
|
||||
* Get the data required to display the module in the course contents view.
|
||||
*
|
||||
|
@ -51,7 +33,7 @@ export interface CoreCourseModuleHandler {
|
|||
* @param {number} sectionId The section ID.
|
||||
* @return {CoreCourseModuleHandlerData} Data to render the module.
|
||||
*/
|
||||
getData(module: any, courseId: number, sectionId: number) : CoreCourseModuleHandlerData;
|
||||
getData(module: any, courseId: number, sectionId: number): CoreCourseModuleHandlerData;
|
||||
|
||||
/**
|
||||
* Get the component to render the module. This is needed to support singleactivity course format.
|
||||
|
@ -60,8 +42,8 @@ export interface CoreCourseModuleHandler {
|
|||
* @param {any} module The module object.
|
||||
* @return {any} The component to use, undefined if not found.
|
||||
*/
|
||||
getMainComponent(course: any, module: any) : any;
|
||||
};
|
||||
getMainComponent(course: any, module: any): any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Data needed to render the module in course contents.
|
||||
|
@ -106,8 +88,8 @@ export interface CoreCourseModuleHandlerData {
|
|||
* @param {number} courseId The course ID.
|
||||
* @param {NavOptions} [options] Options for the navigation.
|
||||
*/
|
||||
action?(event: Event, navCtrl: NavController, module: any, courseId: number, options?: NavOptions) : void;
|
||||
};
|
||||
action?(event: Event, navCtrl: NavController, module: any, courseId: number, options?: NavOptions): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* A button to display in a module item.
|
||||
|
@ -151,26 +133,21 @@ export interface CoreCourseModuleHandlerButton {
|
|||
* @param {any} module The module object.
|
||||
* @param {number} courseId The course ID.
|
||||
*/
|
||||
action(event: Event, navCtrl: NavController, module: any, courseId: number) : void;
|
||||
};
|
||||
action(event: Event, navCtrl: NavController, module: any, courseId: number): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegate to register module handlers.
|
||||
*/
|
||||
@Injectable()
|
||||
export class CoreCourseModuleDelegate {
|
||||
protected logger;
|
||||
protected handlers: {[s: string]: CoreCourseModuleHandler} = {}; // All registered handlers.
|
||||
protected enabledHandlers: {[s: string]: CoreCourseModuleHandler} = {}; // Handlers enabled for the current site.
|
||||
protected lastUpdateHandlersStart: number;
|
||||
export class CoreCourseModuleDelegate extends CoreDelegate {
|
||||
protected handlers: { [s: string]: CoreCourseModuleHandler } = {}; // All registered handlers.
|
||||
protected enabledHandlers: { [s: string]: CoreCourseModuleHandler } = {}; // Handlers enabled for the current site.
|
||||
protected featurePrefix = '$mmCourseDelegate_';
|
||||
|
||||
constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, eventsProvider: CoreEventsProvider,
|
||||
private courseProvider: CoreCourseProvider) {
|
||||
this.logger = logger.getInstance('CoreCourseModuleDelegate');
|
||||
|
||||
eventsProvider.on(CoreEventsProvider.LOGIN, this.updateHandlers.bind(this));
|
||||
eventsProvider.on(CoreEventsProvider.SITE_UPDATED, this.updateHandlers.bind(this));
|
||||
eventsProvider.on(CoreEventsProvider.REMOTE_ADDONS_LOADED, this.updateHandlers.bind(this));
|
||||
constructor(loggerProvider: CoreLoggerProvider, protected sitesProvider: CoreSitesProvider, eventsProvider: CoreEventsProvider,
|
||||
protected courseProvider: CoreCourseProvider) {
|
||||
super('CoreCourseModuleDelegate', loggerProvider, sitesProvider, eventsProvider);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -180,10 +157,10 @@ export class CoreCourseModuleDelegate {
|
|||
* @param {any} module The module object.
|
||||
* @return {any} The component to use, undefined if not found.
|
||||
*/
|
||||
getMainComponent?(course: any, module: any) : any {
|
||||
let handler = this.enabledHandlers[module.modname];
|
||||
getMainComponent?(course: any, module: any): any {
|
||||
const handler = this.enabledHandlers[module.modname];
|
||||
if (handler && handler.getMainComponent) {
|
||||
let component = handler.getMainComponent(course, module);
|
||||
const component = handler.getMainComponent(course, module);
|
||||
if (component) {
|
||||
return component;
|
||||
}
|
||||
|
@ -199,21 +176,21 @@ export class CoreCourseModuleDelegate {
|
|||
* @param {number} sectionId The section ID.
|
||||
* @return {CoreCourseModuleHandlerData} Data to render the module.
|
||||
*/
|
||||
getModuleDataFor(modname: string, module: any, courseId: number, sectionId: number) : CoreCourseModuleHandlerData {
|
||||
getModuleDataFor(modname: string, module: any, courseId: number, sectionId: number): CoreCourseModuleHandlerData {
|
||||
if (typeof this.enabledHandlers[modname] != 'undefined') {
|
||||
return this.enabledHandlers[modname].getData(module, courseId, sectionId);
|
||||
}
|
||||
|
||||
// Return the default data.
|
||||
let defaultData: CoreCourseModuleHandlerData = {
|
||||
const defaultData: CoreCourseModuleHandlerData = {
|
||||
icon: this.courseProvider.getModuleIconSrc(module.modname),
|
||||
title: module.name,
|
||||
class: 'core-course-default-handler core-course-module-' + module.modname + '-handler',
|
||||
action: (event: Event, navCtrl: NavController, module: any, courseId: number, options?: NavOptions) => {
|
||||
action: (event: Event, navCtrl: NavController, module: any, courseId: number, options?: NavOptions): void => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
navCtrl.push('CoreCourseUnsupportedModulePage', {module: module}, options);
|
||||
navCtrl.push('CoreCourseUnsupportedModulePage', { module: module }, options);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -221,7 +198,7 @@ export class CoreCourseModuleDelegate {
|
|||
defaultData.buttons = [{
|
||||
icon: 'open',
|
||||
label: 'core.openinbrowser',
|
||||
action: (e: Event) => {
|
||||
action: (e: Event): void => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.sitesProvider.getCurrentSite().openInBrowserWithAutoLoginIfSameSite(module.url);
|
||||
|
@ -230,16 +207,6 @@ export class CoreCourseModuleDelegate {
|
|||
}
|
||||
|
||||
return defaultData;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if a module has a registered handler (not necessarily enabled).
|
||||
*
|
||||
* @param {string} modname The name of the module type.
|
||||
* @return {boolean} If the controller is installed or not.
|
||||
*/
|
||||
hasHandler(modname: string) : boolean {
|
||||
return typeof this.handlers[modname] !== 'undefined';
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -249,7 +216,7 @@ export class CoreCourseModuleDelegate {
|
|||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<boolean>} Promise resolved with boolean: whether module is disabled.
|
||||
*/
|
||||
isModuleDisabled(modname: string, siteId?: string) : Promise<boolean> {
|
||||
isModuleDisabled(modname: string, siteId?: string): Promise<boolean> {
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
return this.isModuleDisabledInSite(modname, site);
|
||||
});
|
||||
|
@ -262,101 +229,13 @@ export class CoreCourseModuleDelegate {
|
|||
* @param {CoreSite} [site] Site. If not defined, use current site.
|
||||
* @return {boolean} Whether module is disabled.
|
||||
*/
|
||||
isModuleDisabledInSite(modname: string, site?: CoreSite) : boolean {
|
||||
site = site || this.sitesProvider.getCurrentSite();
|
||||
|
||||
isModuleDisabledInSite(modname: string, site?: CoreSite): boolean {
|
||||
if (typeof this.handlers[modname] != 'undefined') {
|
||||
return site.isFeatureDisabled('$mmCourseDelegate_' + this.handlers[modname].name);
|
||||
site = site || this.sitesProvider.getCurrentSite();
|
||||
|
||||
return this.isFeatureDisabled(this.handlers[modname], site);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a time belongs to the last update handlers call.
|
||||
* This is to handle the cases where updateHandlers don't finish in the same order as they're called.
|
||||
*
|
||||
* @param {number} time Time to check.
|
||||
* @return {boolean} Whether it's the last call.
|
||||
*/
|
||||
isLastUpdateCall(time: number) : boolean {
|
||||
if (!this.lastUpdateHandlersStart) {
|
||||
return true;
|
||||
}
|
||||
return time == this.lastUpdateHandlersStart;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a handler.
|
||||
*
|
||||
* @param {CoreCourseModuleHandler} handler The handler to register.
|
||||
* @return {boolean} True if registered successfully, false otherwise.
|
||||
*/
|
||||
registerHandler(handler: CoreCourseModuleHandler) : boolean {
|
||||
if (typeof this.handlers[handler.modname] !== 'undefined') {
|
||||
this.logger.log('There is an addon named \'' + this.handlers[handler.modname].name +
|
||||
'\' already registered as handler for ' + handler.modname);
|
||||
return false;
|
||||
}
|
||||
this.logger.log(`Registered addon '${handler.name}' for '${handler.modname}'`);
|
||||
this.handlers[handler.modname] = handler;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the handler for the current site.
|
||||
*
|
||||
* @param {CoreCourseModuleHandler} handler The handler to check.
|
||||
* @param {number} time Time this update process started.
|
||||
* @return {Promise<void>} Resolved when done.
|
||||
*/
|
||||
protected updateHandler(handler: CoreCourseModuleHandler, time: number) : Promise<void> {
|
||||
let promise,
|
||||
siteId = this.sitesProvider.getCurrentSiteId(),
|
||||
currentSite = this.sitesProvider.getCurrentSite();
|
||||
|
||||
if (!this.sitesProvider.isLoggedIn()) {
|
||||
promise = Promise.reject(null);
|
||||
} else if (currentSite.isFeatureDisabled('$mmCourseDelegate_' + handler.name)) {
|
||||
promise = Promise.resolve(false);
|
||||
} else {
|
||||
promise = Promise.resolve(handler.isEnabled());
|
||||
}
|
||||
|
||||
// Checks if the handler is enabled.
|
||||
return promise.catch(() => {
|
||||
return false;
|
||||
}).then((enabled: boolean) => {
|
||||
// Verify that this call is the last one that was started.
|
||||
if (this.isLastUpdateCall(time) && this.sitesProvider.getCurrentSiteId() === siteId) {
|
||||
if (enabled) {
|
||||
this.enabledHandlers[handler.modname] = handler;
|
||||
} else {
|
||||
delete this.enabledHandlers[handler.modname];
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the handlers for the current site.
|
||||
*
|
||||
* @return {Promise<any>} Resolved when done.
|
||||
*/
|
||||
protected updateHandlers() : Promise<any> {
|
||||
let promises = [],
|
||||
now = Date.now();
|
||||
|
||||
this.logger.debug('Updating handlers for current site.');
|
||||
|
||||
this.lastUpdateHandlersStart = now;
|
||||
|
||||
// Loop over all the handlers.
|
||||
for (let name in this.handlers) {
|
||||
promises.push(this.updateHandler(this.handlers[name], now));
|
||||
}
|
||||
|
||||
return Promise.all(promises).catch(() => {
|
||||
// Never reject.
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
// limitations under the License.
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { NavController } from 'ionic-angular';
|
||||
import { CoreEventsProvider } from '../../../providers/events';
|
||||
import { CoreFileProvider } from '../../../providers/file';
|
||||
import { CoreFilepoolProvider } from '../../../providers/filepool';
|
||||
|
@ -27,6 +26,7 @@ import { CoreSiteWSPreSets } from '../../../classes/site';
|
|||
import { CoreConstants } from '../../constants';
|
||||
import { Md5 } from 'ts-md5/dist/md5';
|
||||
import { Subject, BehaviorSubject, Subscription } from 'rxjs';
|
||||
import { CoreDelegate, CoreDelegateHandler } from '../../../classes/delegate';
|
||||
|
||||
/**
|
||||
* Progress of downloading a list of modules.
|
||||
|
@ -55,19 +55,7 @@ export type CoreCourseModulesProgressFunction = (data: CoreCourseModulesProgress
|
|||
/**
|
||||
* Interface that all course prefetch handlers must implement.
|
||||
*/
|
||||
export interface CoreCourseModulePrefetchHandler {
|
||||
/**
|
||||
* A name to identify the addon.
|
||||
* @type {string}
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* Name of the module. It should match the "modname" of the module returned in core_course_get_contents.
|
||||
* @type {string}
|
||||
*/
|
||||
modname: string;
|
||||
|
||||
export interface CoreCourseModulePrefetchHandler extends CoreDelegateHandler {
|
||||
/**
|
||||
* The handler's component.
|
||||
* @type {string}
|
||||
|
@ -81,13 +69,6 @@ export interface CoreCourseModulePrefetchHandler {
|
|||
*/
|
||||
updatesNames?: RegExp;
|
||||
|
||||
/**
|
||||
* Whether or not the handler is enabled on a site level.
|
||||
*
|
||||
* @return {boolean|Promise<boolean>} A boolean, or a promise resolved with a boolean, indicating if the handler is enabled.
|
||||
*/
|
||||
isEnabled() : boolean|Promise<boolean>;
|
||||
|
||||
/**
|
||||
* Get the download size of a module.
|
||||
*
|
||||
|
@ -97,7 +78,7 @@ export interface CoreCourseModulePrefetchHandler {
|
|||
* @return {Promise<{size: number, total: boolean}>} Promise resolved with the size and a boolean indicating if it was able
|
||||
* to calculate the total size.
|
||||
*/
|
||||
getDownloadSize(module: any, courseId: number, single?: boolean) : Promise<{size: number, total: boolean}>;
|
||||
getDownloadSize(module: any, courseId: number, single?: boolean): Promise<{ size: number, total: boolean }>;
|
||||
|
||||
/**
|
||||
* Prefetch a module.
|
||||
|
@ -118,7 +99,7 @@ export interface CoreCourseModulePrefetchHandler {
|
|||
* @param {number} courseId Course ID the module belongs to.
|
||||
* @return {boolean|Promise<boolean>} Whether the module can use check_updates. The promise should never be rejected.
|
||||
*/
|
||||
canUseCheckUpdates?(module: any, courseId: number) : boolean|Promise<boolean>;
|
||||
canUseCheckUpdates?(module: any, courseId: number): boolean | Promise<boolean>;
|
||||
|
||||
/**
|
||||
* Return the status to show based on current status. E.g. a module might want to show outdated instead of downloaded.
|
||||
|
@ -129,7 +110,7 @@ export interface CoreCourseModulePrefetchHandler {
|
|||
* @param {boolean} canCheck Whether the site allows checking for updates.
|
||||
* @return {string} Status to display.
|
||||
*/
|
||||
determineStatus?(module: any, status: string, canCheck: boolean) : string;
|
||||
determineStatus?(module: any, status: string, canCheck: boolean): string;
|
||||
|
||||
/**
|
||||
* Get the downloaded size of a module. If not defined, we'll use getFiles to calculate it (it can be slow).
|
||||
|
@ -138,7 +119,7 @@ export interface CoreCourseModulePrefetchHandler {
|
|||
* @param {number} courseId Course ID the module belongs to.
|
||||
* @return {number|Promise<number>} Size, or promise resolved with the size.
|
||||
*/
|
||||
getDownloadedSize?(module: any, courseId: number) : number|Promise<number>;
|
||||
getDownloadedSize?(module: any, courseId: number): number | Promise<number>;
|
||||
|
||||
/**
|
||||
* Get the list of files of the module. If not defined, we'll assume they are in module.contents.
|
||||
|
@ -147,7 +128,7 @@ export interface CoreCourseModulePrefetchHandler {
|
|||
* @param {number} courseId Course ID the module belongs to.
|
||||
* @return {any[]|Promise<any[]>} List of files, or promise resolved with the files.
|
||||
*/
|
||||
getFiles?(module: any, courseId: number) : any[]|Promise<any[]>;
|
||||
getFiles?(module: any, courseId: number): any[] | Promise<any[]>;
|
||||
|
||||
/**
|
||||
* Check if a certain module has updates based on the result of check updates.
|
||||
|
@ -157,7 +138,7 @@ export interface CoreCourseModulePrefetchHandler {
|
|||
* @param {any[]} moduleUpdates List of updates for the module.
|
||||
* @return {boolean|Promise<boolean>} Whether the module has updates. The promise should never be rejected.
|
||||
*/
|
||||
hasUpdates?(module: any, courseId: number, moduleUpdates: any[]) : boolean|Promise<boolean>;
|
||||
hasUpdates?(module: any, courseId: number, moduleUpdates: any[]): boolean | Promise<boolean>;
|
||||
|
||||
/**
|
||||
* Invalidate WS calls needed to determine module status. It doesn't need to invalidate check updates.
|
||||
|
@ -167,7 +148,7 @@ export interface CoreCourseModulePrefetchHandler {
|
|||
* @param {number} courseId Course ID the module belongs to.
|
||||
* @return {Promise<any>} Promise resolved when invalidated.
|
||||
*/
|
||||
invalidateModule?(module: any, courseId: number) : Promise<any>;
|
||||
invalidateModule?(module: any, courseId: number): Promise<any>;
|
||||
|
||||
/**
|
||||
* Check if a module can be downloaded. If the function is not defined, we assume that all modules are downloadable.
|
||||
|
@ -176,7 +157,7 @@ export interface CoreCourseModulePrefetchHandler {
|
|||
* @param {number} courseId Course ID the module belongs to.
|
||||
* @return {boolean|Promise<boolean>} Whether the module can be downloaded. The promise should never be rejected.
|
||||
*/
|
||||
isDownloadable?(module: any, courseId: number) : boolean|Promise<boolean>;
|
||||
isDownloadable?(module: any, courseId: number): boolean | Promise<boolean>;
|
||||
|
||||
/**
|
||||
* Load module contents in module.contents if they aren't loaded already. This is meant for resources.
|
||||
|
@ -185,7 +166,7 @@ export interface CoreCourseModulePrefetchHandler {
|
|||
* @param {number} courseId Course ID the module belongs to.
|
||||
* @return {Promise<any>} Promise resolved when done.
|
||||
*/
|
||||
loadContents?(module: any, courseId: number) : Promise<any>;
|
||||
loadContents?(module: any, courseId: number): Promise<any>;
|
||||
|
||||
/**
|
||||
* Remove module downloaded files. If not defined, we'll use getFiles to remove them (slow).
|
||||
|
@ -194,14 +175,14 @@ export interface CoreCourseModulePrefetchHandler {
|
|||
* @param {number} courseId Course ID the module belongs to.
|
||||
* @return {Promise<any>} Promise resolved when done.
|
||||
*/
|
||||
removeFiles?(module: any, courseId: number) : Promise<any>;
|
||||
};
|
||||
removeFiles?(module: any, courseId: number): Promise<any>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegate to register module prefetch handlers.
|
||||
*/
|
||||
@Injectable()
|
||||
export class CoreCourseModulePrefetchDelegate {
|
||||
export class CoreCourseModulePrefetchDelegate extends CoreDelegate {
|
||||
// Variables for database.
|
||||
protected CHECK_UPDATES_TIMES_TABLE = 'check_updates_times';
|
||||
protected checkUpdatesTableSchema = {
|
||||
|
@ -218,31 +199,33 @@ export class CoreCourseModulePrefetchDelegate {
|
|||
notNull: true
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
protected ROOT_CACHE_KEY = 'mmCourse:';
|
||||
|
||||
protected logger;
|
||||
protected handlers: {[s: string]: CoreCourseModulePrefetchHandler} = {}; // All registered handlers.
|
||||
protected enabledHandlers: {[s: string]: CoreCourseModulePrefetchHandler} = {}; // Handlers enabled for the current site.
|
||||
protected handlers: { [s: string]: CoreCourseModulePrefetchHandler } = {}; // All registered handlers.
|
||||
protected enabledHandlers: { [s: string]: CoreCourseModulePrefetchHandler } = {}; // Handlers enabled for the current site.
|
||||
protected statusCache = new CoreCache();
|
||||
protected lastUpdateHandlersStart: number;
|
||||
|
||||
// Promises for check updates, to prevent performing the same request twice at the same time.
|
||||
protected courseUpdatesPromises: {[s: string]: {[s: string]: Promise<any>}} = {};
|
||||
protected courseUpdatesPromises: { [s: string]: { [s: string]: Promise<any> } } = {};
|
||||
|
||||
// Promises and observables for prefetching, to prevent downloading the same section twice at the same time
|
||||
// and notify the progress of the download.
|
||||
protected prefetchData: {[s: string]: {[s: string]: {
|
||||
promise: Promise<any>,
|
||||
observable: Subject<CoreCourseModulesProgress>,
|
||||
subscriptions: Subscription[]
|
||||
}}} = {};
|
||||
// Promises and observables for prefetching, to prevent downloading same section twice at the same time and notify progress.
|
||||
protected prefetchData: {
|
||||
[s: string]: {
|
||||
[s: string]: {
|
||||
promise: Promise<any>,
|
||||
observable: Subject<CoreCourseModulesProgress>,
|
||||
subscriptions: Subscription[]
|
||||
}
|
||||
}
|
||||
} = {};
|
||||
|
||||
constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private eventsProvider: CoreEventsProvider,
|
||||
constructor(loggerProvider: CoreLoggerProvider, protected sitesProvider: CoreSitesProvider, private utils: CoreUtilsProvider,
|
||||
private courseProvider: CoreCourseProvider, private filepoolProvider: CoreFilepoolProvider,
|
||||
private timeUtils: CoreTimeUtilsProvider, private utils: CoreUtilsProvider, private fileProvider: CoreFileProvider) {
|
||||
this.logger = logger.getInstance('CoreCourseModulePrefetchDelegate');
|
||||
private timeUtils: CoreTimeUtilsProvider, private fileProvider: CoreFileProvider,
|
||||
protected eventsProvider: CoreEventsProvider) {
|
||||
super('CoreCourseModulePrefetchDelegate', loggerProvider, sitesProvider);
|
||||
|
||||
this.sitesProvider.createTableFromSchema(this.checkUpdatesTableSchema);
|
||||
}
|
||||
|
@ -252,18 +235,18 @@ export class CoreCourseModulePrefetchDelegate {
|
|||
*
|
||||
* @return {boolean} True if can check updates, false otherwise.
|
||||
*/
|
||||
canCheckUpdates() : boolean {
|
||||
canCheckUpdates(): boolean {
|
||||
return this.sitesProvider.getCurrentSite().wsAvailable('core_course_check_updates');
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Check if a certain module can use core_course_check_updates.
|
||||
*
|
||||
* @param {any} module Module.
|
||||
* @param {number} courseId Course ID the module belongs to.
|
||||
* @return {Promise<boolean>} Promise resolved with boolean: whether the module can use check updates WS.
|
||||
*/
|
||||
canModuleUseCheckUpdates(module: any, courseId: number) : Promise<boolean> {
|
||||
canModuleUseCheckUpdates(module: any, courseId: number): Promise<boolean> {
|
||||
const handler = this.getPrefetchHandlerFor(module);
|
||||
|
||||
if (!handler) {
|
||||
|
@ -282,7 +265,7 @@ export class CoreCourseModulePrefetchDelegate {
|
|||
/**
|
||||
* Clear the status cache.
|
||||
*/
|
||||
clearStatusCache() : void {
|
||||
clearStatusCache(): void {
|
||||
this.statusCache.clear();
|
||||
}
|
||||
|
||||
|
@ -293,8 +276,8 @@ export class CoreCourseModulePrefetchDelegate {
|
|||
* @param {number} courseId Course ID the modules belong to.
|
||||
* @return {Promise<{toCheck: any[], cannotUse: any[]}>} Promise resolved with the lists.
|
||||
*/
|
||||
protected createToCheckList(modules: any[], courseId: number) : Promise<{toCheck: any[], cannotUse: any[]}> {
|
||||
let result = {
|
||||
protected createToCheckList(modules: any[], courseId: number): Promise<{ toCheck: any[], cannotUse: any[] }> {
|
||||
const result = {
|
||||
toCheck: [],
|
||||
cannotUse: []
|
||||
},
|
||||
|
@ -310,7 +293,7 @@ export class CoreCourseModulePrefetchDelegate {
|
|||
result.toCheck.push({
|
||||
contextlevel: 'module',
|
||||
id: module.id,
|
||||
since: data.downloadTime || 0
|
||||
since: data.downloadTime || 0
|
||||
});
|
||||
} else {
|
||||
// Cannot use check updates, add it to the cannotUse array.
|
||||
|
@ -341,7 +324,7 @@ export class CoreCourseModulePrefetchDelegate {
|
|||
* @param {boolean} [canCheck] True if updates can be checked using core_course_check_updates.
|
||||
* @return {string} Module status.
|
||||
*/
|
||||
determineModuleStatus(module: any, status: string, canCheck?: boolean) : string {
|
||||
determineModuleStatus(module: any, status: string, canCheck?: boolean): string {
|
||||
const handler = this.getPrefetchHandlerFor(module),
|
||||
siteId = this.sitesProvider.getCurrentSiteId();
|
||||
|
||||
|
@ -358,6 +341,7 @@ export class CoreCourseModulePrefetchDelegate {
|
|||
return handler.determineStatus(module, status, canCheck);
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
|
@ -369,13 +353,13 @@ export class CoreCourseModulePrefetchDelegate {
|
|||
* @return {Promise<any>} Promise resolved with the updates. If a module is set to false, it means updates cannot be
|
||||
* checked for that module in the current site.
|
||||
*/
|
||||
getCourseUpdates(modules: any[], courseId: number) : Promise<any> {
|
||||
getCourseUpdates(modules: any[], courseId: number): Promise<any> {
|
||||
if (!this.canCheckUpdates()) {
|
||||
return Promise.reject(null);
|
||||
}
|
||||
|
||||
// Check if there's already a getCourseUpdates in progress.
|
||||
let id = <string>Md5.hashAsciiStr(courseId + '#' + JSON.stringify(modules)),
|
||||
const id = <string> Md5.hashAsciiStr(courseId + '#' + JSON.stringify(modules)),
|
||||
siteId = this.sitesProvider.getCurrentSiteId();
|
||||
|
||||
if (this.courseUpdatesPromises[siteId] && this.courseUpdatesPromises[siteId][id]) {
|
||||
|
@ -386,7 +370,7 @@ export class CoreCourseModulePrefetchDelegate {
|
|||
}
|
||||
|
||||
this.courseUpdatesPromises[siteId][id] = this.createToCheckList(modules, courseId).then((data) => {
|
||||
let result = {};
|
||||
const result = {};
|
||||
|
||||
// Mark as false the modules that cannot use check updates WS.
|
||||
data.cannotUse.forEach((module) => {
|
||||
|
@ -400,7 +384,7 @@ export class CoreCourseModulePrefetchDelegate {
|
|||
|
||||
// Get the site, maybe the user changed site.
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
let params = {
|
||||
const params = {
|
||||
courseid: courseId,
|
||||
tocheck: data.toCheck
|
||||
},
|
||||
|
@ -416,22 +400,22 @@ export class CoreCourseModulePrefetchDelegate {
|
|||
}
|
||||
|
||||
// Store the last execution of the check updates call.
|
||||
let entry = {
|
||||
const entry = {
|
||||
courseId: courseId,
|
||||
time: this.timeUtils.timestamp()
|
||||
};
|
||||
site.getDb().insertOrUpdateRecord(this.CHECK_UPDATES_TIMES_TABLE, entry, {courseId: courseId});
|
||||
site.getDb().insertOrUpdateRecord(this.CHECK_UPDATES_TIMES_TABLE, entry, { courseId: courseId });
|
||||
|
||||
return this.treatCheckUpdatesResult(data.toCheck, response, result);
|
||||
}).catch((error) => {
|
||||
// Cannot get updates. Get the cached entries but discard the modules with a download time higher
|
||||
// than the last execution of check updates.
|
||||
return site.getDb().getRecord(this.CHECK_UPDATES_TIMES_TABLE, {courseId: courseId}).then((entry) => {
|
||||
// Cannot get updates.
|
||||
// Get cached entries but discard modules with a download time higher than the last execution of check updates.
|
||||
return site.getDb().getRecord(this.CHECK_UPDATES_TIMES_TABLE, { courseId: courseId }).then((entry) => {
|
||||
preSets.getCacheUsingCacheKey = true;
|
||||
preSets.omitExpires = true;
|
||||
|
||||
return site.read('core_course_check_updates', params, preSets).then((response) => {
|
||||
if (!response || typeof response.instances == 'undefined') {
|
||||
if (!response || typeof response.instances == 'undefined') {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
|
@ -451,20 +435,19 @@ export class CoreCourseModulePrefetchDelegate {
|
|||
return this.courseUpdatesPromises[siteId][id];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check for updates in a course.
|
||||
*
|
||||
* @param {number} courseId Course ID the modules belong to.
|
||||
* @return {Promise<any>} Promise resolved with the updates.
|
||||
*/
|
||||
getCourseUpdatesByCourseId(courseId: number) : Promise<any> {
|
||||
getCourseUpdatesByCourseId(courseId: number): Promise<any> {
|
||||
if (!this.canCheckUpdates()) {
|
||||
return Promise.reject(null);
|
||||
}
|
||||
|
||||
// Get course sections and all their modules.
|
||||
return this.courseProvider.getSections(courseId, false, true, {omitExpires: true}).then((sections) => {
|
||||
return this.courseProvider.getSections(courseId, false, true, { omitExpires: true }).then((sections) => {
|
||||
return this.getCourseUpdates(this.courseProvider.getSectionsModules(sections), courseId);
|
||||
});
|
||||
}
|
||||
|
@ -475,7 +458,7 @@ export class CoreCourseModulePrefetchDelegate {
|
|||
* @param {number} courseId Course ID.
|
||||
* @return {string} Cache key.
|
||||
*/
|
||||
protected getCourseUpdatesCacheKey(courseId: number) : string {
|
||||
protected getCourseUpdatesCacheKey(courseId: number): string {
|
||||
return this.ROOT_CACHE_KEY + 'courseUpdates:' + courseId;
|
||||
}
|
||||
|
||||
|
@ -487,7 +470,7 @@ export class CoreCourseModulePrefetchDelegate {
|
|||
* @return {Promise<{size: number, total: boolean}>} Promise resolved with the size and a boolean indicating if it was able
|
||||
* to calculate the total size.
|
||||
*/
|
||||
getDownloadSize(modules: any[], courseId: number) : Promise<{size: number, total: boolean}> {
|
||||
getDownloadSize(modules: any[], courseId: number): Promise<{ size: number, total: boolean }> {
|
||||
// Get the status of each module.
|
||||
return this.getModulesStatus(modules, courseId).then((data) => {
|
||||
const downloadableModules = data[CoreConstants.NOT_DOWNLOADED].concat(data[CoreConstants.OUTDATED]),
|
||||
|
@ -519,16 +502,16 @@ export class CoreCourseModulePrefetchDelegate {
|
|||
* @return {Promise<{size: number, total: boolean}>} Promise resolved with the size and a boolean indicating if it was able
|
||||
* to calculate the total size.
|
||||
*/
|
||||
getModuleDownloadSize(module: any, courseId: number, single?: boolean) : Promise<{size: number, total: boolean}> {
|
||||
getModuleDownloadSize(module: any, courseId: number, single?: boolean): Promise<{ size: number, total: boolean }> {
|
||||
const handler = this.getPrefetchHandlerFor(module);
|
||||
let downloadSize,
|
||||
packageId,
|
||||
handler = this.getPrefetchHandlerFor(module);
|
||||
packageId;
|
||||
|
||||
// Check if the module has a prefetch handler.
|
||||
if (handler) {
|
||||
return this.isModuleDownloadable(module, courseId).then((downloadable) => {
|
||||
if (!downloadable) {
|
||||
return {size: 0, total: true};
|
||||
return { size: 0, total: true };
|
||||
}
|
||||
|
||||
packageId = this.filepoolProvider.getPackageId(handler.component, module.id);
|
||||
|
@ -544,12 +527,13 @@ export class CoreCourseModulePrefetchDelegate {
|
|||
if (cachedSize) {
|
||||
return cachedSize;
|
||||
}
|
||||
|
||||
return Promise.reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return Promise.resolve({size: 0, total: false});
|
||||
return Promise.resolve({ size: 0, total: false });
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -559,11 +543,11 @@ export class CoreCourseModulePrefetchDelegate {
|
|||
* @param {number} courseId Course ID the module belongs to.
|
||||
* @return {Promise<number>} Promise resolved with the size.
|
||||
*/
|
||||
getModuleDownloadedSize(module: any, courseId: number) : Promise<number> {
|
||||
getModuleDownloadedSize(module: any, courseId: number): Promise<number> {
|
||||
const handler = this.getPrefetchHandlerFor(module);
|
||||
let downloadedSize,
|
||||
packageId,
|
||||
promise,
|
||||
handler = this.getPrefetchHandlerFor(module);
|
||||
promise;
|
||||
|
||||
// Check if the module has a prefetch handler.
|
||||
if (handler) {
|
||||
|
@ -584,9 +568,9 @@ export class CoreCourseModulePrefetchDelegate {
|
|||
} else {
|
||||
// Handler doesn't implement it, get the module files and check if they're downloaded.
|
||||
promise = this.getModuleFiles(module, courseId).then((files) => {
|
||||
let siteId = this.sitesProvider.getCurrentSiteId(),
|
||||
promises = [],
|
||||
size = 0;
|
||||
const siteId = this.sitesProvider.getCurrentSiteId(),
|
||||
promises = [];
|
||||
let size = 0;
|
||||
|
||||
// Retrieve file size if it's downloaded.
|
||||
files.forEach((file) => {
|
||||
|
@ -631,7 +615,7 @@ export class CoreCourseModulePrefetchDelegate {
|
|||
* @param {number} courseId Course ID the module belongs to.
|
||||
* @return {Promise<any[]>} Promise resolved with the list of files.
|
||||
*/
|
||||
getModuleFiles(module: any, courseId: number) : Promise<any[]> {
|
||||
getModuleFiles(module: any, courseId: number): Promise<any[]> {
|
||||
const handler = this.getPrefetchHandlerFor(module);
|
||||
|
||||
if (handler.getFiles) {
|
||||
|
@ -658,16 +642,16 @@ export class CoreCourseModulePrefetchDelegate {
|
|||
* @param {number} [sectionId] ID of the section the module belongs to.
|
||||
* @return {Promise<string>} Promise resolved with the status.
|
||||
*/
|
||||
getModuleStatus(module: any, courseId: number, updates?: any, refresh?: boolean, sectionId?: number) : Promise<string> {
|
||||
let handler = this.getPrefetchHandlerFor(module),
|
||||
getModuleStatus(module: any, courseId: number, updates?: any, refresh?: boolean, sectionId?: number): Promise<string> {
|
||||
const handler = this.getPrefetchHandlerFor(module),
|
||||
siteId = this.sitesProvider.getCurrentSiteId(),
|
||||
canCheck = this.canCheckUpdates();
|
||||
|
||||
if (handler) {
|
||||
// Check if the status is cached.
|
||||
let component = handler.component,
|
||||
packageId = this.filepoolProvider.getPackageId(component, module.id),
|
||||
status = this.statusCache.getValue(packageId, 'status'),
|
||||
const component = handler.component,
|
||||
packageId = this.filepoolProvider.getPackageId(component, module.id);
|
||||
let status = this.statusCache.getValue(packageId, 'status'),
|
||||
updateStatus = true,
|
||||
promise;
|
||||
|
||||
|
@ -714,6 +698,7 @@ export class CoreCourseModulePrefetchDelegate {
|
|||
|
||||
// Has updates, mark the module as outdated.
|
||||
status = CoreConstants.OUTDATED;
|
||||
|
||||
return this.filepoolProvider.storePackageStatus(siteId, component, module.id, status).catch(() => {
|
||||
// Ignore errors.
|
||||
}).then(() => {
|
||||
|
@ -722,11 +707,13 @@ export class CoreCourseModulePrefetchDelegate {
|
|||
}).catch(() => {
|
||||
// Error checking if module has updates.
|
||||
const status = this.statusCache.getValue(packageId, 'status', true);
|
||||
|
||||
return this.determineModuleStatus(module, status, canCheck);
|
||||
});
|
||||
}, () => {
|
||||
// Error getting updates, show the stored status.
|
||||
updateStatus = false;
|
||||
|
||||
return currentStatus;
|
||||
});
|
||||
});
|
||||
|
@ -734,6 +721,7 @@ export class CoreCourseModulePrefetchDelegate {
|
|||
if (updateStatus) {
|
||||
this.updateStatusCache(status, courseId, component, module.id, sectionId);
|
||||
}
|
||||
|
||||
return this.determineModuleStatus(module, status, canCheck);
|
||||
});
|
||||
}
|
||||
|
@ -758,12 +746,12 @@ export class CoreCourseModulePrefetchDelegate {
|
|||
* - CoreConstants.DOWNLOADING (any[]) Modules with state DOWNLOADING.
|
||||
* - CoreConstants.OUTDATED (any[]) Modules with state OUTDATED.
|
||||
*/
|
||||
getModulesStatus(modules: any[], courseId: number, sectionId?: number, refresh?: boolean) : any {
|
||||
let promises = [],
|
||||
status = CoreConstants.NOT_DOWNLOADABLE,
|
||||
getModulesStatus(modules: any[], courseId: number, sectionId?: number, refresh?: boolean): any {
|
||||
const promises = [],
|
||||
result: any = {
|
||||
total: 0
|
||||
};
|
||||
let status = CoreConstants.NOT_DOWNLOADABLE;
|
||||
|
||||
// Init result.
|
||||
result[CoreConstants.NOT_DOWNLOADED] = [];
|
||||
|
@ -779,9 +767,9 @@ export class CoreCourseModulePrefetchDelegate {
|
|||
|
||||
modules.forEach((module) => {
|
||||
// Check if the module has a prefetch handler.
|
||||
let handler = this.getPrefetchHandlerFor(module);
|
||||
const handler = this.getPrefetchHandlerFor(module);
|
||||
if (handler) {
|
||||
let packageId = this.filepoolProvider.getPackageId(handler.component, module.id);
|
||||
const packageId = this.filepoolProvider.getPackageId(handler.component, module.id);
|
||||
|
||||
promises.push(this.getModuleStatus(module, courseId, updates, refresh).then((modStatus) => {
|
||||
if (modStatus != CoreConstants.NOT_DOWNLOADABLE) {
|
||||
|
@ -811,6 +799,7 @@ export class CoreCourseModulePrefetchDelegate {
|
|||
|
||||
return Promise.all(promises).then(() => {
|
||||
result.status = status;
|
||||
|
||||
return result;
|
||||
});
|
||||
});
|
||||
|
@ -823,13 +812,13 @@ export class CoreCourseModulePrefetchDelegate {
|
|||
* @param {number} courseId Course ID the module belongs to.
|
||||
* @return {Promise<{status: string, downloadTime?: number}>} Promise resolved with the data.
|
||||
*/
|
||||
protected getModuleStatusAndDownloadTime(module: any, courseId: number) : Promise<{status: string, downloadTime?: number}> {
|
||||
let handler = this.getPrefetchHandlerFor(module),
|
||||
protected getModuleStatusAndDownloadTime(module: any, courseId: number): Promise<{ status: string, downloadTime?: number }> {
|
||||
const handler = this.getPrefetchHandlerFor(module),
|
||||
siteId = this.sitesProvider.getCurrentSiteId();
|
||||
|
||||
if (handler) {
|
||||
// Get the status from the cache.
|
||||
let packageId = this.filepoolProvider.getPackageId(handler.component, module.id),
|
||||
const packageId = this.filepoolProvider.getPackageId(handler.component, module.id),
|
||||
status = this.statusCache.getValue(packageId, 'status');
|
||||
|
||||
if (typeof status != 'undefined' && status != CoreConstants.DOWNLOADED) {
|
||||
|
@ -840,7 +829,7 @@ export class CoreCourseModulePrefetchDelegate {
|
|||
}
|
||||
|
||||
// Check if the module is downloadable.
|
||||
return this.isModuleDownloadable(module, courseId).then((downloadable: boolean) : any => {
|
||||
return this.isModuleDownloadable(module, courseId).then((downloadable: boolean): any => {
|
||||
if (!downloadable) {
|
||||
return {
|
||||
status: CoreConstants.NOT_DOWNLOADABLE
|
||||
|
@ -869,7 +858,7 @@ export class CoreCourseModulePrefetchDelegate {
|
|||
* @param {any} module The module to work on.
|
||||
* @return {CoreCourseModulePrefetchHandler} Prefetch handler.
|
||||
*/
|
||||
getPrefetchHandlerFor(module: any) : CoreCourseModulePrefetchHandler {
|
||||
getPrefetchHandlerFor(module: any): CoreCourseModulePrefetchHandler {
|
||||
return this.enabledHandlers[module.modname];
|
||||
}
|
||||
|
||||
|
@ -879,7 +868,7 @@ export class CoreCourseModulePrefetchDelegate {
|
|||
* @param {number} courseId Course ID.
|
||||
* @return {Promise<any>} Promise resolved when data is invalidated.
|
||||
*/
|
||||
invalidateCourseUpdates(courseId: number) : Promise<any> {
|
||||
invalidateCourseUpdates(courseId: number): Promise<any> {
|
||||
return this.sitesProvider.getCurrentSite().invalidateWsCacheForKey(this.getCourseUpdatesCacheKey(courseId));
|
||||
}
|
||||
|
||||
|
@ -890,8 +879,8 @@ export class CoreCourseModulePrefetchDelegate {
|
|||
* @param {number} courseId Course ID.
|
||||
* @return {Promise<any>} Promise resolved when modules are invalidated.
|
||||
*/
|
||||
invalidateModules(modules: any[], courseId: number) : Promise<any> {
|
||||
let promises = [];
|
||||
invalidateModules(modules: any[], courseId: number): Promise<any> {
|
||||
const promises = [];
|
||||
|
||||
modules.forEach((module) => {
|
||||
const handler = this.getPrefetchHandlerFor(module);
|
||||
|
@ -917,7 +906,7 @@ export class CoreCourseModulePrefetchDelegate {
|
|||
*
|
||||
* @param {any} module Module to be invalidated.
|
||||
*/
|
||||
invalidateModuleStatusCache(module: any) : void {
|
||||
invalidateModuleStatusCache(module: any): void {
|
||||
const handler = this.getPrefetchHandlerFor(module);
|
||||
if (handler) {
|
||||
this.statusCache.invalidate(this.filepoolProvider.getPackageId(handler.component, module.id));
|
||||
|
@ -930,23 +919,10 @@ export class CoreCourseModulePrefetchDelegate {
|
|||
* @param {string} id An ID to identify the download.
|
||||
* @return {boolean} True if it's being downloaded, false otherwise.
|
||||
*/
|
||||
isBeingDownloaded(id: string) : boolean {
|
||||
isBeingDownloaded(id: string): boolean {
|
||||
const siteId = this.sitesProvider.getCurrentSiteId();
|
||||
return !!(this.prefetchData[siteId] && this.prefetchData[siteId][id]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a time belongs to the last update handlers call.
|
||||
* This is to handle the cases where updateHandlers don't finish in the same order as they're called.
|
||||
*
|
||||
* @param {number} time Time to check.
|
||||
* @return {boolean} Whether it's the last call.
|
||||
*/
|
||||
isLastUpdateCall(time: number) : boolean {
|
||||
if (!this.lastUpdateHandlersStart) {
|
||||
return true;
|
||||
}
|
||||
return time == this.lastUpdateHandlersStart;
|
||||
return !!(this.prefetchData[siteId] && this.prefetchData[siteId][id]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -956,13 +932,12 @@ export class CoreCourseModulePrefetchDelegate {
|
|||
* @param {Number} courseId Course ID the module belongs to.
|
||||
* @return {Promise<boolean>} Promise resolved with true if downloadable, false otherwise.
|
||||
*/
|
||||
isModuleDownloadable(module: any, courseId: number) : Promise<boolean> {
|
||||
let handler = this.getPrefetchHandlerFor(module),
|
||||
promise;
|
||||
isModuleDownloadable(module: any, courseId: number): Promise<boolean> {
|
||||
const handler = this.getPrefetchHandlerFor(module);
|
||||
|
||||
if (handler) {
|
||||
if (typeof handler.isDownloadable == 'function') {
|
||||
let packageId = this.filepoolProvider.getPackageId(handler.component, module.id),
|
||||
const packageId = this.filepoolProvider.getPackageId(handler.component, module.id),
|
||||
downloadable = this.statusCache.getValue(packageId, 'downloadable');
|
||||
|
||||
if (typeof downloadable != 'undefined') {
|
||||
|
@ -993,14 +968,14 @@ export class CoreCourseModulePrefetchDelegate {
|
|||
* @param {any} updates Result of getCourseUpdates.
|
||||
* @return {Promise<boolean>} Promise resolved with boolean: whether the module has updates.
|
||||
*/
|
||||
moduleHasUpdates(module: any, courseId: number, updates: any) : Promise<boolean> {
|
||||
let handler = this.getPrefetchHandlerFor(module),
|
||||
moduleHasUpdates(module: any, courseId: number, updates: any): Promise<boolean> {
|
||||
const handler = this.getPrefetchHandlerFor(module),
|
||||
moduleUpdates = updates[module.id];
|
||||
|
||||
if (handler && handler.hasUpdates) {
|
||||
// Handler implements its own function to check the updates, use it.
|
||||
return Promise.resolve(handler.hasUpdates(module, courseId, moduleUpdates));
|
||||
} else if (!moduleUpdates || !moduleUpdates.updates || !moduleUpdates.updates.length) {
|
||||
} else if (!moduleUpdates || !moduleUpdates.updates || !moduleUpdates.updates.length) {
|
||||
// Module doesn't have any update.
|
||||
return Promise.resolve(false);
|
||||
} else if (handler && handler.updatesNames && handler.updatesNames.test) {
|
||||
|
@ -1026,13 +1001,14 @@ export class CoreCourseModulePrefetchDelegate {
|
|||
* @param {boolean} [single] True if we're downloading a single module, false if we're downloading a whole section.
|
||||
* @return {Promise<any>} Promise resolved when finished.
|
||||
*/
|
||||
prefetchModule(module: any, courseId: number, single?: boolean) : Promise<any> {
|
||||
prefetchModule(module: any, courseId: number, single?: boolean): Promise<any> {
|
||||
const handler = this.getPrefetchHandlerFor(module);
|
||||
|
||||
// Check if the module has a prefetch handler.
|
||||
if (handler) {
|
||||
return handler.prefetch(module, courseId, single);
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
|
@ -1046,7 +1022,7 @@ export class CoreCourseModulePrefetchDelegate {
|
|||
* @param {CoreCourseModulesProgressFunction} [onProgress] Function to call everytime a module is downloaded.
|
||||
* @return {Promise<any>} Promise resolved when all modules have been prefetched.
|
||||
*/
|
||||
prefetchModules(id: string, modules: any[], courseId: number, onProgress?: CoreCourseModulesProgressFunction) : Promise<any> {
|
||||
prefetchModules(id: string, modules: any[], courseId: number, onProgress?: CoreCourseModulesProgressFunction): Promise<any> {
|
||||
|
||||
const siteId = this.sitesProvider.getCurrentSiteId(),
|
||||
currentData = this.prefetchData[siteId] && this.prefetchData[siteId][id];
|
||||
|
@ -1056,22 +1032,21 @@ export class CoreCourseModulePrefetchDelegate {
|
|||
if (onProgress) {
|
||||
currentData.subscriptions.push(currentData.observable.subscribe(onProgress));
|
||||
}
|
||||
|
||||
return currentData.promise;
|
||||
}
|
||||
|
||||
let promises = [],
|
||||
count = 0,
|
||||
let count = 0;
|
||||
const promises = [],
|
||||
total = modules.length,
|
||||
moduleIds = modules.map((module) => {
|
||||
return module.id;
|
||||
});
|
||||
|
||||
// Initialize the prefetch data.
|
||||
const prefetchData = {
|
||||
observable: new BehaviorSubject<CoreCourseModulesProgress>({count: count, total: total}),
|
||||
promise: undefined,
|
||||
subscriptions: []
|
||||
};
|
||||
}),
|
||||
prefetchData = {
|
||||
observable: new BehaviorSubject<CoreCourseModulesProgress>({ count: count, total: total }),
|
||||
promise: undefined,
|
||||
subscriptions: []
|
||||
};
|
||||
|
||||
if (onProgress) {
|
||||
prefetchData.observable.subscribe(onProgress);
|
||||
|
@ -1087,12 +1062,12 @@ export class CoreCourseModulePrefetchDelegate {
|
|||
}
|
||||
|
||||
return handler.prefetch(module, courseId).then(() => {
|
||||
let index = moduleIds.indexOf(id);
|
||||
const index = moduleIds.indexOf(id);
|
||||
if (index > -1) {
|
||||
// It's one of the modules we were expecting to download.
|
||||
moduleIds.splice(index, 1);
|
||||
count++;
|
||||
prefetchData.observable.next({count: count, total: total});
|
||||
prefetchData.observable.next({ count: count, total: total });
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
@ -1117,23 +1092,6 @@ export class CoreCourseModulePrefetchDelegate {
|
|||
return prefetchData.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a handler.
|
||||
*
|
||||
* @param {CoreCourseModulePrefetchHandler} handler The handler to register.
|
||||
* @return {boolean} True if registered successfully, false otherwise.
|
||||
*/
|
||||
registerHandler(handler: CoreCourseModulePrefetchHandler) : boolean {
|
||||
if (typeof this.handlers[handler.modname] !== 'undefined') {
|
||||
this.logger.log('There is an addon named \'' + this.handlers[handler.modname].name +
|
||||
'\' already registered as a prefetch handler for ' + handler.modname);
|
||||
return false;
|
||||
}
|
||||
this.logger.log(`Registered addon '${handler.name}' as a prefetch handler for '${handler.modname}'`);
|
||||
this.handlers[handler.modname] = handler;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove module Files from handler.
|
||||
*
|
||||
|
@ -1141,10 +1099,10 @@ export class CoreCourseModulePrefetchDelegate {
|
|||
* @param {number} courseId Course ID the module belongs to.
|
||||
* @return {Promise<void>} Promise resolved when done.
|
||||
*/
|
||||
removeModuleFiles(module: any, courseId: number) : Promise<void> {
|
||||
let handler = this.getPrefetchHandlerFor(module),
|
||||
siteId = this.sitesProvider.getCurrentSiteId(),
|
||||
promise;
|
||||
removeModuleFiles(module: any, courseId: number): Promise<void> {
|
||||
const handler = this.getPrefetchHandlerFor(module),
|
||||
siteId = this.sitesProvider.getCurrentSiteId();
|
||||
let promise;
|
||||
|
||||
if (handler && handler.removeFiles) {
|
||||
// Handler implements a method to remove the files, use it.
|
||||
|
@ -1152,12 +1110,13 @@ export class CoreCourseModulePrefetchDelegate {
|
|||
} else {
|
||||
// No method to remove files, use get files to try to remove the files.
|
||||
promise = this.getModuleFiles(module, courseId).then((files) => {
|
||||
let promises = [];
|
||||
const promises = [];
|
||||
files.forEach((file) => {
|
||||
promises.push(this.filepoolProvider.removeFileByUrl(siteId, file.url || file.fileurl).catch(() => {
|
||||
// Ignore errors.
|
||||
}));
|
||||
});
|
||||
|
||||
return Promise.all(promises);
|
||||
});
|
||||
}
|
||||
|
@ -1178,7 +1137,7 @@ export class CoreCourseModulePrefetchDelegate {
|
|||
* @param {string} id An ID to identify the download.
|
||||
* @param {CoreCourseModulesProgressFunction} onProgress Function to call everytime a module is downloaded.
|
||||
*/
|
||||
setOnProgress(id: string, onProgress: CoreCourseModulesProgressFunction) : void {
|
||||
setOnProgress(id: string, onProgress: CoreCourseModulesProgressFunction): void {
|
||||
const siteId = this.sitesProvider.getCurrentSiteId(),
|
||||
currentData = this.prefetchData[siteId] && this.prefetchData[siteId][id];
|
||||
|
||||
|
@ -1198,7 +1157,7 @@ export class CoreCourseModulePrefetchDelegate {
|
|||
* after this time will be ignored.
|
||||
* @return {any} Result.
|
||||
*/
|
||||
protected treatCheckUpdatesResult(toCheckList: any[], response: any, result: any, previousTime?: number) : any {
|
||||
protected treatCheckUpdatesResult(toCheckList: any[], response: any, result: any, previousTime?: number): any {
|
||||
// Format the response to index it by module ID.
|
||||
this.utils.arrayToObject(response.instances, 'id', result);
|
||||
|
||||
|
@ -1221,60 +1180,6 @@ export class CoreCourseModulePrefetchDelegate {
|
|||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the enabled handlers for the current site.
|
||||
*
|
||||
* @param {CoreCourseModulePrefetchHandler} handler The handler to treat.
|
||||
* @param {number} time Time this update process started.
|
||||
* @return {Promise<void>} Resolved when done.
|
||||
*/
|
||||
updateHandler(handler: CoreCourseModulePrefetchHandler, time: number) : Promise<void> {
|
||||
let promise,
|
||||
siteId = this.sitesProvider.getCurrentSiteId();
|
||||
|
||||
if (!siteId) {
|
||||
promise = Promise.reject(null);
|
||||
} else {
|
||||
promise = Promise.resolve(handler.isEnabled());
|
||||
}
|
||||
|
||||
// Checks if the prefetch is enabled.
|
||||
return promise.catch(() => {
|
||||
return false;
|
||||
}).then((enabled: boolean) => {
|
||||
// Verify that this call is the last one that was started.
|
||||
// Check that site hasn't changed since the check started.
|
||||
if (this.isLastUpdateCall(time) && this.sitesProvider.getCurrentSiteId() == siteId) {
|
||||
if (enabled) {
|
||||
this.enabledHandlers[handler.modname] = handler;
|
||||
} else {
|
||||
delete this.enabledHandlers[handler.modname];
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the handlers for the current site.
|
||||
*
|
||||
* @return {Promise<any>} Resolved when done.
|
||||
*/
|
||||
updateHandlers() : Promise<any> {
|
||||
const promises = [],
|
||||
now = Date.now();
|
||||
|
||||
this.lastUpdateHandlersStart = now;
|
||||
|
||||
// Loop over all the handlers.
|
||||
for (let name in this.handlers) {
|
||||
promises.push(this.updateHandler(this.handlers[name], now));
|
||||
}
|
||||
|
||||
return Promise.all(promises).catch(() => {
|
||||
// Never reject.
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the status of a module in the "cache".
|
||||
*
|
||||
|
@ -1284,10 +1189,11 @@ export class CoreCourseModulePrefetchDelegate {
|
|||
* @param {string|number} [componentId] An ID to use in conjunction with the component.
|
||||
* @param {number} [sectionId] Section ID of the module.
|
||||
*/
|
||||
updateStatusCache(status: string, courseId: number, component: string, componentId?: string|number, sectionId?: number) : void {
|
||||
let notify,
|
||||
packageId = this.filepoolProvider.getPackageId(component, componentId),
|
||||
updateStatusCache(status: string, courseId: number, component: string, componentId?: string | number, sectionId?: number)
|
||||
: void {
|
||||
const packageId = this.filepoolProvider.getPackageId(component, componentId),
|
||||
cachedStatus = this.statusCache.getValue(packageId, 'status', true);
|
||||
let notify;
|
||||
|
||||
// If the status has changed, notify that the section has changed.
|
||||
notify = typeof cachedStatus != 'undefined' && cachedStatus !== status;
|
||||
|
|
|
@ -13,35 +13,24 @@
|
|||
// limitations under the License.
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CoreDelegate, CoreDelegateHandler } from '../../../classes/delegate';
|
||||
import { CoreEventsProvider } from '../../../providers/events';
|
||||
import { CoreLoggerProvider } from '../../../providers/logger';
|
||||
import { CoreSitesProvider } from '../../../providers/sites';
|
||||
import { CoreUtilsProvider, PromiseDefer } from '../../../providers/utils/utils';
|
||||
import { CoreCoursesProvider } from './courses';
|
||||
import { CoreCoursesProvider } from '../../courses/providers/courses';
|
||||
import { CoreCourseProvider } from './course';
|
||||
|
||||
/**
|
||||
* Interface that all courses handlers must implement.
|
||||
* Interface that all course options handlers must implement.
|
||||
*/
|
||||
export interface CoreCoursesHandler {
|
||||
/**
|
||||
* Name of the handler.
|
||||
* @type {string}
|
||||
*/
|
||||
name: string;
|
||||
|
||||
export interface CoreCourseOptionsHandler extends CoreDelegateHandler {
|
||||
/**
|
||||
* The highest priority is displayed first.
|
||||
* @type {number}
|
||||
*/
|
||||
priority: number;
|
||||
|
||||
/**
|
||||
* Whether or not the handler is enabled on a site level.
|
||||
*
|
||||
* @return {boolean|Promise<boolean>} True or promise resolved with true if enabled.
|
||||
*/
|
||||
isEnabled(): boolean|Promise<boolean>;
|
||||
|
||||
/**
|
||||
* Whether or not the handler is enabled for a certain course.
|
||||
* For perfomance reasons, do NOT call WebServices in here, call them in shouldDisplayForCourse.
|
||||
|
@ -52,7 +41,7 @@ export interface CoreCoursesHandler {
|
|||
* @param {any} [admOptions] Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions.
|
||||
* @return {boolean|Promise<boolean>} True or promise resolved with true if enabled.
|
||||
*/
|
||||
isEnabledForCourse(courseId: number, accessData: any, navOptions?: any, admOptions?: any) : boolean|Promise<boolean>;
|
||||
isEnabledForCourse(courseId: number, accessData: any, navOptions?: any, admOptions?: any): boolean | Promise<boolean>;
|
||||
|
||||
/**
|
||||
* Whether or not the handler should be displayed for a course. If not implemented, assume it's true.
|
||||
|
@ -63,15 +52,15 @@ export interface CoreCoursesHandler {
|
|||
* @param {any} [admOptions] Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions.
|
||||
* @return {boolean|Promise<boolean>} True or promise resolved with true if enabled.
|
||||
*/
|
||||
shouldDisplayForCourse(courseId: number, accessData: any, navOptions?: any, admOptions?: any) : boolean|Promise<boolean>;
|
||||
shouldDisplayForCourse(courseId: number, accessData: any, navOptions?: any, admOptions?: any): boolean | Promise<boolean>;
|
||||
|
||||
/**
|
||||
* Returns the data needed to render the handler.
|
||||
*
|
||||
* @param {number} courseId The course ID.
|
||||
* @return {CoreCoursesHandlerData} Data.
|
||||
* @return {CoreCourseOptionsHandlerData} Data.
|
||||
*/
|
||||
getDisplayData?(courseId: number): CoreCoursesHandlerData;
|
||||
getDisplayData?(courseId: number): CoreCourseOptionsHandlerData;
|
||||
|
||||
/**
|
||||
* Should invalidate the data to determine if the handler is enabled for a certain course.
|
||||
|
@ -81,7 +70,7 @@ export interface CoreCoursesHandler {
|
|||
* @param {any} [admOptions] Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions.
|
||||
* @return {Promise<any>} Promise resolved when done.
|
||||
*/
|
||||
invalidateEnabledForCourse?(courseId: number, navOptions?: any, admOptions?: any) : Promise<any>;
|
||||
invalidateEnabledForCourse?(courseId: number, navOptions?: any, admOptions?: any): Promise<any>;
|
||||
|
||||
/**
|
||||
* Called when a course is downloaded. It should prefetch all the data to be able to see the addon in offline.
|
||||
|
@ -89,13 +78,13 @@ export interface CoreCoursesHandler {
|
|||
* @param {any} course The course.
|
||||
* @return {Promise<any>} Promise resolved when done.
|
||||
*/
|
||||
prefetch?(course: any) : Promise<any>;
|
||||
};
|
||||
prefetch?(course: any): Promise<any>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Data needed to render a course handler. It's returned by the handler.
|
||||
*/
|
||||
export interface CoreCoursesHandlerData {
|
||||
export interface CoreCourseOptionsHandlerData {
|
||||
/**
|
||||
* Title to display for the handler.
|
||||
* @type {string}
|
||||
|
@ -120,17 +109,17 @@ export interface CoreCoursesHandlerData {
|
|||
* @param {any} course The course.
|
||||
*/
|
||||
action(course: any): void;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Data returned by the delegate for each handler.
|
||||
*/
|
||||
export interface CoreCoursesHandlerToDisplay {
|
||||
export interface CoreCourseOptionsHandlerToDisplay {
|
||||
/**
|
||||
* Data to display.
|
||||
* @type {CoreCoursesHandlerData}
|
||||
* @type {CoreCourseOptionsHandlerData}
|
||||
*/
|
||||
data: CoreCoursesHandlerData;
|
||||
data: CoreCourseOptionsHandlerData;
|
||||
|
||||
/**
|
||||
* The highest priority is displayed first.
|
||||
|
@ -144,30 +133,30 @@ export interface CoreCoursesHandlerToDisplay {
|
|||
* @param {any} course The course.
|
||||
* @return {Promise<any>} Promise resolved when done.
|
||||
*/
|
||||
prefetch?(course: any) : Promise<any>;
|
||||
};
|
||||
prefetch?(course: any): Promise<any>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Service to interact with plugins to be shown in each course.
|
||||
* Service to interact with plugins to be shown in each course (participants, learning plans, ...).
|
||||
*/
|
||||
@Injectable()
|
||||
export class CoreCoursesDelegate {
|
||||
protected logger;
|
||||
protected handlers: {[s: string]: CoreCoursesHandler} = {}; // All registered handlers.
|
||||
protected enabledHandlers: {[s: string]: CoreCoursesHandler} = {}; // Handlers enabled for the current site.
|
||||
protected loaded: {[courseId: number]: boolean} = {};
|
||||
protected lastUpdateHandlersStart: number;
|
||||
export class CoreCourseOptionsDelegate extends CoreDelegate {
|
||||
protected handlers: { [s: string]: CoreCourseOptionsHandler } = {}; // All registered handlers.
|
||||
protected enabledHandlers: { [s: string]: CoreCourseOptionsHandler } = {}; // Handlers enabled for the current site.
|
||||
protected loaded: { [courseId: number]: boolean } = {};
|
||||
protected lastUpdateHandlersForCoursesStart: any = {};
|
||||
protected coursesHandlers: {[courseId: number]: {
|
||||
access?: any, navOptions?: any, admOptions?: any, deferred?: PromiseDefer, enabledHandlers?: CoreCoursesHandler[]}} = {};
|
||||
protected coursesHandlers: {
|
||||
[courseId: number]: {
|
||||
access?: any, navOptions?: any, admOptions?: any, deferred?: PromiseDefer, enabledHandlers?: CoreCourseOptionsHandler[]
|
||||
}
|
||||
} = {};
|
||||
|
||||
constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private eventsProvider: CoreEventsProvider,
|
||||
private coursesProvider: CoreCoursesProvider, private utils: CoreUtilsProvider) {
|
||||
this.logger = logger.getInstance('CoreMainMenuDelegate');
|
||||
protected featurePrefix = '$mmCoursesDelegate_';
|
||||
|
||||
constructor(loggerProvider: CoreLoggerProvider, protected sitesProvider: CoreSitesProvider, private utils: CoreUtilsProvider,
|
||||
protected eventsProvider: CoreEventsProvider, private coursesProvider: CoreCoursesProvider) {
|
||||
super('CoreCourseOptionsDelegate', loggerProvider, sitesProvider, eventsProvider);
|
||||
|
||||
eventsProvider.on(CoreEventsProvider.LOGIN, this.updateHandlers.bind(this));
|
||||
eventsProvider.on(CoreEventsProvider.SITE_UPDATED, this.updateHandlers.bind(this));
|
||||
eventsProvider.on(CoreEventsProvider.REMOTE_ADDONS_LOADED, this.updateHandlers.bind(this));
|
||||
eventsProvider.on(CoreEventsProvider.LOGOUT, () => {
|
||||
this.clearCoursesHandlers();
|
||||
});
|
||||
|
@ -179,16 +168,16 @@ export class CoreCoursesDelegate {
|
|||
* @param {number} courseId The course ID to check.
|
||||
* @return {boolean} True if handlers are loaded, false otherwise.
|
||||
*/
|
||||
areHandlersLoaded(courseId: number) : boolean {
|
||||
areHandlersLoaded(courseId: number): boolean {
|
||||
return !!this.loaded[courseId];
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all courses handlers.
|
||||
* Clear all course options handlers.
|
||||
*
|
||||
* @param {number} [courseId] The course ID. If not defined, all handlers will be cleared.
|
||||
*/
|
||||
protected clearCoursesHandlers(courseId?: number) : void {
|
||||
protected clearCoursesHandlers(courseId?: number): void {
|
||||
if (courseId) {
|
||||
this.loaded[courseId] = false;
|
||||
delete this.coursesHandlers[courseId];
|
||||
|
@ -204,22 +193,22 @@ export class CoreCoursesDelegate {
|
|||
* @param {number} [courseId] The course ID. If not defined, all handlers will be cleared.
|
||||
* @return {Promise<any>} Promise resolved when done.
|
||||
*/
|
||||
clearAndInvalidateCoursesOptions(courseId?: number) : Promise<any> {
|
||||
var promises = [];
|
||||
clearAndInvalidateCoursesOptions(courseId?: number): Promise<any> {
|
||||
const promises = [];
|
||||
|
||||
this.eventsProvider.trigger(CoreCoursesProvider.EVENT_MY_COURSES_REFRESHED);
|
||||
|
||||
// Invalidate course enabled data for the handlers that are enabled at site level.
|
||||
if (courseId) {
|
||||
// Invalidate only options for this course.
|
||||
promises.push(this.coursesProvider.invalidateCoursesOptions([courseId]));
|
||||
promises.push(this.coursesProvider.invalidateCoursesAdminAndNavOptions([courseId]));
|
||||
promises.push(this.invalidateCourseHandlers(courseId));
|
||||
} else {
|
||||
// Invalidate all options.
|
||||
promises.push(this.coursesProvider.invalidateUserNavigationOptions());
|
||||
promises.push(this.coursesProvider.invalidateUserAdministrationOptions());
|
||||
|
||||
for (let cId in this.coursesHandlers) {
|
||||
for (const cId in this.coursesHandlers) {
|
||||
promises.push(this.invalidateCourseHandlers(parseInt(cId, 10)));
|
||||
}
|
||||
}
|
||||
|
@ -237,17 +226,17 @@ export class CoreCoursesDelegate {
|
|||
* @param {any} accessData Access type and data. Default, guest, ...
|
||||
* @param {any} [navOptions] Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions.
|
||||
* @param {any} [admOptions] Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions.
|
||||
* @return {Promise<CoreCoursesHandler[]>} Promise resolved with array of handlers.
|
||||
* @return {Promise<CoreCourseOptionsHandler[]>} Promise resolved with array of handlers.
|
||||
*/
|
||||
protected getHandlersForAccess(courseId: number, refresh: boolean, accessData: any, navOptions?: any,
|
||||
admOptions?: any) : Promise<CoreCoursesHandler[]> {
|
||||
admOptions?: any): Promise<CoreCourseOptionsHandler[]> {
|
||||
|
||||
// If the handlers aren't loaded, do not refresh.
|
||||
if (!this.loaded[courseId]) {
|
||||
refresh = false;
|
||||
}
|
||||
|
||||
if (refresh || !this.coursesHandlers[courseId] || this.coursesHandlers[courseId].access.type != accessData.type) {
|
||||
if (refresh || !this.coursesHandlers[courseId] || this.coursesHandlers[courseId].access.type != accessData.type) {
|
||||
if (!this.coursesHandlers[courseId]) {
|
||||
this.coursesHandlers[courseId] = {};
|
||||
}
|
||||
|
@ -272,14 +261,14 @@ export class CoreCoursesDelegate {
|
|||
* @param {boolean} [isGuest] Whether it's guest.
|
||||
* @param {any} [navOptions] Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions.
|
||||
* @param {any} [admOptions] Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions.
|
||||
* @return {Promise<CoreCoursesHandlerToDisplay[]>} Promise resolved with array of handlers.
|
||||
* @return {Promise<CoreCourseOptionsHandlerToDisplay[]>} Promise resolved with array of handlers.
|
||||
*/
|
||||
getHandlersToDisplay(course: any, refresh?: boolean, isGuest?: boolean, navOptions?: any, admOptions?: any) :
|
||||
Promise<CoreCoursesHandlerToDisplay[]> {
|
||||
getHandlersToDisplay(course: any, refresh?: boolean, isGuest?: boolean, navOptions?: any, admOptions?: any):
|
||||
Promise<CoreCourseOptionsHandlerToDisplay[]> {
|
||||
course.id = parseInt(course.id, 10);
|
||||
|
||||
let accessData = {
|
||||
type: isGuest ? CoreCoursesProvider.ACCESS_GUEST : CoreCoursesProvider.ACCESS_DEFAULT
|
||||
const accessData = {
|
||||
type: isGuest ? CoreCourseProvider.ACCESS_GUEST : CoreCourseProvider.ACCESS_DEFAULT
|
||||
};
|
||||
|
||||
if (navOptions) {
|
||||
|
@ -293,14 +282,14 @@ export class CoreCoursesDelegate {
|
|||
// Call getHandlersForAccess to make sure the handlers have been loaded.
|
||||
return this.getHandlersForAccess(course.id, refresh, accessData, course.navOptions, course.admOptions);
|
||||
}).then(() => {
|
||||
let handlersToDisplay: CoreCoursesHandlerToDisplay[] = [],
|
||||
promises = [],
|
||||
promise;
|
||||
const handlersToDisplay: CoreCourseOptionsHandlerToDisplay[] = [],
|
||||
promises = [];
|
||||
let promise;
|
||||
|
||||
this.coursesHandlers[course.id].enabledHandlers.forEach((handler) => {
|
||||
if (handler.shouldDisplayForCourse) {
|
||||
promise = Promise.resolve(handler.shouldDisplayForCourse(
|
||||
course.id, accessData, course.navOptions, course.admOptions));
|
||||
course.id, accessData, course.navOptions, course.admOptions));
|
||||
} else {
|
||||
// Not implemented, assume it should be displayed.
|
||||
promise = Promise.resolve(true);
|
||||
|
@ -335,7 +324,7 @@ export class CoreCoursesDelegate {
|
|||
* @param {boolean} [refresh] True if it should refresh the list.
|
||||
* @return {Promise<boolean>} Promise resolved with boolean: true if it has handlers, false otherwise.
|
||||
*/
|
||||
hasHandlersForCourse(course: any, refresh?: boolean) : Promise<boolean> {
|
||||
hasHandlersForCourse(course: any, refresh?: boolean): Promise<boolean> {
|
||||
// Load course options if missing.
|
||||
return this.loadCourseOptions(course, refresh).then(() => {
|
||||
return this.hasHandlersForDefault(course.id, refresh, course.navOptions, course.admOptions);
|
||||
|
@ -351,11 +340,12 @@ export class CoreCoursesDelegate {
|
|||
* @param {any} [admOptions] Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions.
|
||||
* @return {Promise<boolean>} Promise resolved with boolean: true if it has handlers, false otherwise.
|
||||
*/
|
||||
hasHandlersForDefault(courseId: number, refresh?: boolean, navOptions?: any, admOptions?: any) : Promise<boolean> {
|
||||
hasHandlersForDefault(courseId: number, refresh?: boolean, navOptions?: any, admOptions?: any): Promise<boolean> {
|
||||
// Default access.
|
||||
let accessData = {
|
||||
type: CoreCoursesProvider.ACCESS_DEFAULT
|
||||
const accessData = {
|
||||
type: CoreCourseProvider.ACCESS_DEFAULT
|
||||
};
|
||||
|
||||
return this.getHandlersForAccess(courseId, refresh, accessData, navOptions, admOptions).then((handlers) => {
|
||||
return !!(handlers && handlers.length);
|
||||
});
|
||||
|
@ -370,11 +360,12 @@ export class CoreCoursesDelegate {
|
|||
* @param {any} [admOptions] Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions.
|
||||
* @return {Promise<boolean>} Promise resolved with boolean: true if it has handlers, false otherwise.
|
||||
*/
|
||||
hasHandlersForGuest(courseId: number, refresh?: boolean, navOptions?: any, admOptions?: any) : Promise<boolean> {
|
||||
hasHandlersForGuest(courseId: number, refresh?: boolean, navOptions?: any, admOptions?: any): Promise<boolean> {
|
||||
// Guest access.
|
||||
var accessData = {
|
||||
type: CoreCoursesProvider.ACCESS_GUEST
|
||||
const accessData = {
|
||||
type: CoreCourseProvider.ACCESS_GUEST
|
||||
};
|
||||
|
||||
return this.getHandlersForAccess(courseId, refresh, accessData, navOptions, admOptions).then((handlers) => {
|
||||
return !!(handlers && handlers.length);
|
||||
});
|
||||
|
@ -386,8 +377,8 @@ export class CoreCoursesDelegate {
|
|||
* @param {number} courseId Course ID.
|
||||
* @return {Promise<any>} Promise resolved when done.
|
||||
*/
|
||||
invalidateCourseHandlers(courseId: number) : Promise<any> {
|
||||
let promises = [],
|
||||
invalidateCourseHandlers(courseId: number): Promise<any> {
|
||||
const promises = [],
|
||||
courseData = this.coursesHandlers[courseId];
|
||||
|
||||
if (!courseData) {
|
||||
|
@ -397,27 +388,13 @@ export class CoreCoursesDelegate {
|
|||
courseData.enabledHandlers.forEach((handler) => {
|
||||
if (handler && handler.invalidateEnabledForCourse) {
|
||||
promises.push(Promise.resolve(
|
||||
handler.invalidateEnabledForCourse(courseId, courseData.navOptions, courseData.admOptions)));
|
||||
handler.invalidateEnabledForCourse(courseId, courseData.navOptions, courseData.admOptions)));
|
||||
}
|
||||
});
|
||||
|
||||
return this.utils.allPromises(promises);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a time belongs to the last update handlers call.
|
||||
* This is to handle the cases where updateHandlers don't finish in the same order as they're called.
|
||||
*
|
||||
* @param {number} time Time to check.
|
||||
* @return {boolean} Whether it's the last call.
|
||||
*/
|
||||
isLastUpdateCall(time: number) : boolean {
|
||||
if (!this.lastUpdateHandlersStart) {
|
||||
return true;
|
||||
}
|
||||
return time == this.lastUpdateHandlersStart;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a time belongs to the last update handlers for course call.
|
||||
* This is to handle the cases where updateHandlersForCourse don't finish in the same order as they're called.
|
||||
|
@ -426,10 +403,11 @@ export class CoreCoursesDelegate {
|
|||
* @param {number} time Time to check.
|
||||
* @return {boolean} Whether it's the last call.
|
||||
*/
|
||||
isLastUpdateCourseCall(courseId: number, time: number) : boolean {
|
||||
isLastUpdateCourseCall(courseId: number, time: number): boolean {
|
||||
if (!this.lastUpdateHandlersForCoursesStart[courseId]) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return time == this.lastUpdateHandlersForCoursesStart[courseId];
|
||||
}
|
||||
|
||||
|
@ -440,9 +418,9 @@ export class CoreCoursesDelegate {
|
|||
* @param {boolean} [refresh] True if it should refresh the list.
|
||||
* @return {Promise<void>} Promise resolved when done.
|
||||
*/
|
||||
protected loadCourseOptions(course: any, refresh?: boolean) : Promise<void> {
|
||||
protected loadCourseOptions(course: any, refresh?: boolean): Promise<void> {
|
||||
if (typeof course.navOptions == 'undefined' || typeof course.admOptions == 'undefined' || refresh) {
|
||||
return this.coursesProvider.getCoursesOptions([course.id]).then((options) => {
|
||||
return this.coursesProvider.getCoursesAdminAndNavOptions([course.id]).then((options) => {
|
||||
course.navOptions = options.navOptions[course.id];
|
||||
course.admOptions = options.admOptions[course.id];
|
||||
});
|
||||
|
@ -452,91 +430,18 @@ export class CoreCoursesDelegate {
|
|||
}
|
||||
|
||||
/**
|
||||
* Register a handler.
|
||||
* Update handlers for each course.
|
||||
*
|
||||
* @param {CoreCoursesHandler} handler The handler to register.
|
||||
* @return {boolean} True if registered successfully, false otherwise.
|
||||
* @param {string} [siteId] Site ID.
|
||||
*/
|
||||
registerHandler(handler: CoreCoursesHandler) : boolean {
|
||||
if (typeof this.handlers[handler.name] !== 'undefined') {
|
||||
this.logger.log(`Addon '${handler.name}' already registered`);
|
||||
return false;
|
||||
}
|
||||
this.logger.log(`Registered addon '${handler.name}'`);
|
||||
this.handlers[handler.name] = handler;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the handler for the current site.
|
||||
*
|
||||
* @param {CoreInitHandler} handler The handler to check.
|
||||
* @param {number} time Time this update process started.
|
||||
* @return {Promise<void>} Resolved when done.
|
||||
*/
|
||||
protected updateHandler(handler: CoreCoursesHandler, time: number) : Promise<void> {
|
||||
let promise,
|
||||
siteId = this.sitesProvider.getCurrentSiteId(),
|
||||
currentSite = this.sitesProvider.getCurrentSite();
|
||||
|
||||
if (!this.sitesProvider.isLoggedIn()) {
|
||||
promise = Promise.reject(null);
|
||||
} else if (currentSite.isFeatureDisabled('$mmCoursesDelegate_' + handler.name)) {
|
||||
promise = Promise.resolve(false);
|
||||
} else {
|
||||
promise = Promise.resolve(handler.isEnabled());
|
||||
}
|
||||
|
||||
// Checks if the handler is enabled.
|
||||
return promise.catch(() => {
|
||||
return false;
|
||||
}).then((enabled: boolean) => {
|
||||
// Verify that this call is the last one that was started.
|
||||
// Check that site hasn't changed since the check started.
|
||||
if (this.isLastUpdateCall(time) && this.sitesProvider.getCurrentSiteId() === siteId) {
|
||||
if (enabled) {
|
||||
this.enabledHandlers[handler.name] = handler;
|
||||
} else {
|
||||
delete this.enabledHandlers[handler.name];
|
||||
}
|
||||
updateData(siteId?: string): void {
|
||||
if (this.sitesProvider.getCurrentSiteId() === siteId) {
|
||||
// Update handlers for all courses.
|
||||
for (const courseId in this.coursesHandlers) {
|
||||
const handler = this.coursesHandlers[courseId];
|
||||
this.updateHandlersForCourse(parseInt(courseId, 10), handler.access, handler.navOptions, handler.admOptions);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the handlers for the current site.
|
||||
*
|
||||
* @return {Promise<void>} Resolved when done.
|
||||
*/
|
||||
protected updateHandlers() : Promise<void> {
|
||||
let promises = [],
|
||||
siteId = this.sitesProvider.getCurrentSiteId(),
|
||||
now = Date.now();
|
||||
|
||||
this.logger.debug('Updating handlers for current site.');
|
||||
|
||||
this.lastUpdateHandlersStart = now;
|
||||
|
||||
// Loop over all the handlers.
|
||||
for (let name in this.handlers) {
|
||||
promises.push(this.updateHandler(this.handlers[name], now));
|
||||
}
|
||||
|
||||
return Promise.all(promises).then(() => {
|
||||
return true;
|
||||
}, () => {
|
||||
// Never reject.
|
||||
return true;
|
||||
}).then(() => {
|
||||
// Verify that this call is the last one that was started.
|
||||
if (this.isLastUpdateCall(now) && this.sitesProvider.getCurrentSiteId() === siteId) {
|
||||
// Update handlers for all courses.
|
||||
for (let courseId in this.coursesHandlers) {
|
||||
let handler = this.coursesHandlers[courseId];
|
||||
this.updateHandlersForCourse(parseInt(courseId, 10), handler.access, handler.navOptions, handler.admOptions);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -549,28 +454,28 @@ export class CoreCoursesDelegate {
|
|||
* @return {Promise} Resolved when updated.
|
||||
* @protected
|
||||
*/
|
||||
updateHandlersForCourse(courseId: number, accessData: any, navOptions?: any, admOptions?: any) : Promise<any> {
|
||||
let promises = [],
|
||||
updateHandlersForCourse(courseId: number, accessData: any, navOptions?: any, admOptions?: any): Promise<any> {
|
||||
const promises = [],
|
||||
enabledForCourse = [],
|
||||
siteId = this.sitesProvider.getCurrentSiteId(),
|
||||
now = Date.now();
|
||||
|
||||
this.lastUpdateHandlersForCoursesStart[courseId] = now;
|
||||
|
||||
for (let name in this.enabledHandlers) {
|
||||
let handler = this.enabledHandlers[name];
|
||||
for (const name in this.enabledHandlers) {
|
||||
const handler = this.enabledHandlers[name];
|
||||
|
||||
// Checks if the handler is enabled for the user.
|
||||
promises.push(Promise.resolve(handler.isEnabledForCourse(courseId, accessData, navOptions, admOptions))
|
||||
.then(function(enabled) {
|
||||
if (enabled) {
|
||||
enabledForCourse.push(handler);
|
||||
} else {
|
||||
return Promise.reject(null);
|
||||
}
|
||||
}).catch(() => {
|
||||
// Nothing to do here, it is not enabled for this user.
|
||||
}));
|
||||
.then((enabled) => {
|
||||
if (enabled) {
|
||||
enabledForCourse.push(handler);
|
||||
} else {
|
||||
return Promise.reject(null);
|
||||
}
|
||||
}).catch(() => {
|
||||
// Nothing to do here, it is not enabled for this user.
|
||||
}));
|
||||
}
|
||||
|
||||
return Promise.all(promises).then(() => {
|
||||
|
@ -590,5 +495,5 @@ export class CoreCoursesDelegate {
|
|||
this.coursesHandlers[courseId].deferred.resolve();
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
|
@ -31,14 +31,15 @@ import { CoreCoursesProvider } from '../../providers/courses';
|
|||
export class CoreCoursesCourseListItemComponent implements OnInit {
|
||||
@Input() course: any; // The course to render.
|
||||
|
||||
constructor(private navCtrl: NavController, private translate: TranslateService, private coursesProvider: CoreCoursesProvider) {}
|
||||
constructor(private navCtrl: NavController, private translate: TranslateService, private coursesProvider: CoreCoursesProvider) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Component being initialized.
|
||||
*/
|
||||
ngOnInit() {
|
||||
ngOnInit(): void {
|
||||
// Check if the user is enrolled in the course.
|
||||
return this.coursesProvider.getUserCourse(this.course.id).then(() => {
|
||||
this.coursesProvider.getUserCourse(this.course.id).then(() => {
|
||||
this.course.isEnrolled = true;
|
||||
}).catch(() => {
|
||||
this.course.isEnrolled = false;
|
||||
|
@ -74,9 +75,10 @@ export class CoreCoursesCourseListItemComponent implements OnInit {
|
|||
|
||||
/**
|
||||
* Open a course.
|
||||
*
|
||||
* @param {any} course The course to open.
|
||||
*/
|
||||
openCourse(course) {
|
||||
openCourse(course: any): void {
|
||||
this.navCtrl.push('CoreCoursesCoursePreviewPage', {course: course});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
|
||||
import { Component, Input, OnInit, OnDestroy } from '@angular/core';
|
||||
import { NavController } from 'ionic-angular';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { CoreEventsProvider } from '../../../../providers/events';
|
||||
import { CoreSitesProvider } from '../../../../providers/sites';
|
||||
import { CoreDomUtilsProvider } from '../../../../providers/utils/dom';
|
||||
|
@ -45,7 +44,7 @@ export class CoreCoursesCourseProgressComponent implements OnInit, OnDestroy {
|
|||
protected isDestroyed = false;
|
||||
protected courseStatusObserver;
|
||||
|
||||
constructor(private navCtrl: NavController, private translate: TranslateService, private courseHelper: CoreCourseHelperProvider,
|
||||
constructor(private navCtrl: NavController, private courseHelper: CoreCourseHelperProvider,
|
||||
private courseFormatDelegate: CoreCourseFormatDelegate, private domUtils: CoreDomUtilsProvider,
|
||||
private courseProvider: CoreCourseProvider, eventsProvider: CoreEventsProvider, sitesProvider: CoreSitesProvider) {
|
||||
// Listen for status change in course.
|
||||
|
@ -59,7 +58,7 @@ export class CoreCoursesCourseProgressComponent implements OnInit, OnDestroy {
|
|||
/**
|
||||
* Component being initialized.
|
||||
*/
|
||||
ngOnInit() {
|
||||
ngOnInit(): void {
|
||||
// Determine course prefetch icon.
|
||||
this.courseHelper.getCourseStatusIcon(this.course.id).then((icon) => {
|
||||
this.prefetchCourseData.prefetchCourseIcon = icon;
|
||||
|
@ -84,8 +83,10 @@ export class CoreCoursesCourseProgressComponent implements OnInit, OnDestroy {
|
|||
|
||||
/**
|
||||
* Open a course.
|
||||
*
|
||||
* @param {any} course The course to open.
|
||||
*/
|
||||
openCourse(course) {
|
||||
openCourse(course: any): void {
|
||||
this.courseFormatDelegate.openCourse(this.navCtrl, course);
|
||||
}
|
||||
|
||||
|
@ -94,7 +95,7 @@ export class CoreCoursesCourseProgressComponent implements OnInit, OnDestroy {
|
|||
*
|
||||
* @param {Event} e Click event.
|
||||
*/
|
||||
prefetchCourse(e: Event) {
|
||||
prefetchCourse(e: Event): void {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
|
@ -102,13 +103,13 @@ export class CoreCoursesCourseProgressComponent implements OnInit, OnDestroy {
|
|||
if (!this.isDestroyed) {
|
||||
this.domUtils.showErrorModalDefault(error, 'core.course.errordownloadingcourse', true);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Component destroyed.
|
||||
*/
|
||||
ngOnDestroy() {
|
||||
ngOnDestroy(): void {
|
||||
this.isDestroyed = true;
|
||||
|
||||
if (this.courseStatusObserver) {
|
||||
|
|
|
@ -31,7 +31,7 @@ import * as moment from 'moment';
|
|||
})
|
||||
export class CoreCoursesOverviewEventsComponent implements OnChanges {
|
||||
@Input() events: any[]; // The events to render.
|
||||
@Input() showCourse?: boolean|string; // Whether to show the course name.
|
||||
@Input() showCourse?: boolean | string; // Whether to show the course name.
|
||||
@Input() canLoadMore?: boolean; // Whether more events can be loaded.
|
||||
@Output() loadMore: EventEmitter<void>; // Notify that more events should be loaded.
|
||||
|
||||
|
@ -52,7 +52,7 @@ export class CoreCoursesOverviewEventsComponent implements OnChanges {
|
|||
/**
|
||||
* Detect changes on input properties.
|
||||
*/
|
||||
ngOnChanges(changes: {[name: string]: SimpleChange}) {
|
||||
ngOnChanges(changes: {[name: string]: SimpleChange}): void {
|
||||
this.showCourse = this.utils.isTrueOrOne(this.showCourse);
|
||||
|
||||
if (changes.events) {
|
||||
|
@ -65,8 +65,9 @@ export class CoreCoursesOverviewEventsComponent implements OnChanges {
|
|||
*
|
||||
* @param {number} start Number of days to start getting events from today. E.g. -1 will get events from yesterday.
|
||||
* @param {number} [end] Number of days after the start.
|
||||
* @return {any[]} Filtered events.
|
||||
*/
|
||||
protected filterEventsByTime(start: number, end?: number) {
|
||||
protected filterEventsByTime(start: number, end?: number): any[] {
|
||||
start = moment().add(start, 'days').unix();
|
||||
end = typeof end != 'undefined' ? moment().add(end, 'days').unix() : end;
|
||||
|
||||
|
@ -78,6 +79,7 @@ export class CoreCoursesOverviewEventsComponent implements OnChanges {
|
|||
return start <= event.timesort;
|
||||
}).map((event) => {
|
||||
event.iconUrl = this.courseProvider.getModuleIconSrc(event.icon.component);
|
||||
|
||||
return event;
|
||||
});
|
||||
}
|
||||
|
@ -85,7 +87,7 @@ export class CoreCoursesOverviewEventsComponent implements OnChanges {
|
|||
/**
|
||||
* Update the events displayed.
|
||||
*/
|
||||
protected updateEvents() {
|
||||
protected updateEvents(): void {
|
||||
this.empty = !this.events || this.events.length <= 0;
|
||||
if (!this.empty) {
|
||||
this.recentlyOverdue = this.filterEventsByTime(-14, 0);
|
||||
|
@ -99,7 +101,7 @@ export class CoreCoursesOverviewEventsComponent implements OnChanges {
|
|||
/**
|
||||
* Load more events clicked.
|
||||
*/
|
||||
loadMoreEvents() {
|
||||
loadMoreEvents(): void {
|
||||
this.loadingMore = true;
|
||||
this.loadMore.emit();
|
||||
}
|
||||
|
@ -110,14 +112,14 @@ export class CoreCoursesOverviewEventsComponent implements OnChanges {
|
|||
* @param {Event} e Click event.
|
||||
* @param {string} url Url of the action.
|
||||
*/
|
||||
action(e: Event, url: string) {
|
||||
action(e: Event, url: string): void {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
// Fix URL format.
|
||||
url = this.textUtils.decodeHTMLEntities(url);
|
||||
|
||||
let modal = this.domUtils.showModalLoading();
|
||||
const modal = this.domUtils.showModalLoading();
|
||||
this.contentLinksHelper.handleLink(url, undefined, this.navCtrl).then((treated) => {
|
||||
if (!treated) {
|
||||
return this.sitesProvider.getCurrentSite().openInBrowserWithAutoLoginIfSameSite(url);
|
||||
|
@ -125,7 +127,5 @@ export class CoreCoursesOverviewEventsComponent implements OnChanges {
|
|||
}).finally(() => {
|
||||
modal.dismiss();
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,6 @@ import { NgModule } from '@angular/core';
|
|||
import { CoreCoursesProvider } from './providers/courses';
|
||||
import { CoreCoursesMainMenuHandler } from './providers/mainmenu-handler';
|
||||
import { CoreCoursesMyOverviewProvider } from './providers/my-overview';
|
||||
import { CoreCoursesDelegate } from './providers/delegate';
|
||||
import { CoreCoursesCourseLinkHandler } from './providers/course-link-handler';
|
||||
import { CoreCoursesIndexLinkHandler } from './providers/courses-index-link-handler';
|
||||
import { CoreCoursesMyOverviewLinkHandler } from './providers/my-overview-link-handler';
|
||||
|
@ -31,7 +30,6 @@ import { CoreContentLinksDelegate } from '../contentlinks/providers/delegate';
|
|||
CoreCoursesProvider,
|
||||
CoreCoursesMainMenuHandler,
|
||||
CoreCoursesMyOverviewProvider,
|
||||
CoreCoursesDelegate,
|
||||
CoreCoursesCourseLinkHandler,
|
||||
CoreCoursesIndexLinkHandler,
|
||||
CoreCoursesMyOverviewLinkHandler
|
||||
|
|
|
@ -21,7 +21,7 @@ import { CoreCoursesProvider } from '../../providers/courses';
|
|||
/**
|
||||
* Page that displays available courses in current site.
|
||||
*/
|
||||
@IonicPage({segment: "core-courses-available-courses"})
|
||||
@IonicPage({ segment: 'core-courses-available-courses' })
|
||||
@Component({
|
||||
selector: 'page-core-courses-available-courses',
|
||||
templateUrl: 'available-courses.html',
|
||||
|
@ -31,12 +31,12 @@ export class CoreCoursesAvailableCoursesPage {
|
|||
coursesLoaded: boolean;
|
||||
|
||||
constructor(private coursesProvider: CoreCoursesProvider, private domUtils: CoreDomUtilsProvider,
|
||||
private sitesProvider: CoreSitesProvider) {}
|
||||
private sitesProvider: CoreSitesProvider) { }
|
||||
|
||||
/**
|
||||
* View loaded.
|
||||
*/
|
||||
ionViewDidLoad() {
|
||||
ionViewDidLoad(): void {
|
||||
this.loadCourses().finally(() => {
|
||||
this.coursesLoaded = true;
|
||||
});
|
||||
|
@ -44,9 +44,12 @@ export class CoreCoursesAvailableCoursesPage {
|
|||
|
||||
/**
|
||||
* Load the courses.
|
||||
*
|
||||
* @return {Promise<any>} Promise resolved when done.
|
||||
*/
|
||||
protected loadCourses() {
|
||||
protected loadCourses(): Promise<any> {
|
||||
const frontpageCourseId = this.sitesProvider.getCurrentSite().getSiteHomeId();
|
||||
|
||||
return this.coursesProvider.getCoursesByField().then((courses) => {
|
||||
this.courses = courses.filter((course) => {
|
||||
return course.id != frontpageCourseId;
|
||||
|
@ -61,8 +64,8 @@ export class CoreCoursesAvailableCoursesPage {
|
|||
*
|
||||
* @param {any} refresher Refresher.
|
||||
*/
|
||||
refreshCourses(refresher: any) {
|
||||
let promises = [];
|
||||
refreshCourses(refresher: any): void {
|
||||
const promises = [];
|
||||
|
||||
promises.push(this.coursesProvider.invalidateUserCourses());
|
||||
promises.push(this.coursesProvider.invalidateCoursesByField());
|
||||
|
@ -72,5 +75,5 @@ export class CoreCoursesAvailableCoursesPage {
|
|||
refresher.complete();
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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({segment: "core-courses-categories"})
|
||||
@IonicPage({ segment: 'core-courses-categories' })
|
||||
@Component({
|
||||
selector: 'page-core-courses-categories',
|
||||
templateUrl: 'categories.html',
|
||||
|
@ -47,7 +47,7 @@ export class CoreCoursesCategoriesPage {
|
|||
/**
|
||||
* View loaded.
|
||||
*/
|
||||
ionViewDidLoad() {
|
||||
ionViewDidLoad(): void {
|
||||
this.fetchCategories().finally(() => {
|
||||
this.categoriesLoaded = true;
|
||||
});
|
||||
|
@ -55,8 +55,10 @@ export class CoreCoursesCategoriesPage {
|
|||
|
||||
/**
|
||||
* Fetch the categories.
|
||||
*
|
||||
* @return {Promise<any>} Promise resolved when done.
|
||||
*/
|
||||
protected fetchCategories() {
|
||||
protected fetchCategories(): Promise<any> {
|
||||
return this.coursesProvider.getCategories(this.categoryId, true).then((cats) => {
|
||||
this.currentCategory = undefined;
|
||||
|
||||
|
@ -69,10 +71,11 @@ export class CoreCoursesCategoriesPage {
|
|||
});
|
||||
|
||||
// Sort by depth and sortorder to avoid problems formatting Tree.
|
||||
cats.sort((a,b) => {
|
||||
cats.sort((a, b) => {
|
||||
if (a.depth == b.depth) {
|
||||
return (a.sortorder > b.sortorder) ? 1 : ((b.sortorder > a.sortorder) ? -1 : 0);
|
||||
}
|
||||
|
||||
return a.depth > b.depth ? 1 : -1;
|
||||
});
|
||||
|
||||
|
@ -97,8 +100,8 @@ export class CoreCoursesCategoriesPage {
|
|||
*
|
||||
* @param {any} refresher Refresher.
|
||||
*/
|
||||
refreshCategories(refresher: any) {
|
||||
let promises = [];
|
||||
refreshCategories(refresher: any): void {
|
||||
const promises = [];
|
||||
|
||||
promises.push(this.coursesProvider.invalidateUserCourses());
|
||||
promises.push(this.coursesProvider.invalidateCategories(this.categoryId, true));
|
||||
|
@ -116,7 +119,7 @@ export class CoreCoursesCategoriesPage {
|
|||
*
|
||||
* @param {number} categoryId The category ID.
|
||||
*/
|
||||
openCategory(categoryId: number) {
|
||||
this.navCtrl.push('CoreCoursesCategoriesPage', {categoryId: categoryId});
|
||||
openCategory(categoryId: number): void {
|
||||
this.navCtrl.push('CoreCoursesCategoriesPage', { categoryId: categoryId });
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,19 +21,20 @@
|
|||
<core-format-text [text]="course.summary" maxHeight="120"></core-format-text>
|
||||
</ion-item>
|
||||
|
||||
<a ion-item text-wrap *ngIf="course.contacts && course.contacts.length" detail-none>
|
||||
<p class="item-heading">{{ 'core.teachers' | translate }}</p>
|
||||
<p *ngFor="let contact of course.contacts">{{contact.fullname}}</p>
|
||||
</a>
|
||||
<ng-container text-wrap *ngIf="course.contacts && course.contacts.length">
|
||||
<ion-item-divider color="light">{{ 'core.teachers' | translate }}</ion-item-divider>
|
||||
<a ion-item text-wrap *ngFor="let contact of course.contacts" core-user-link userId="{{contact.id}}" courseId="{{isEnrolled ? course.id : null}}" [attr.aria-label]="'core.viewprofile' | translate">{{contact.fullname}}</a>
|
||||
<ion-item-divider color="light"></ion-item-divider>
|
||||
</ng-container>
|
||||
<core-file *ngFor="let file of course.overviewfiles" [file]="file" [component]="component" [componentId]="course.id"></core-file>
|
||||
<div *ngIf="!isEnrolled" detail-none>
|
||||
<ion-item text-wrap *ngFor="let instance of selfEnrolInstances">
|
||||
<p class="item-heading">{{ instance.name }}</p>
|
||||
<h2>{{ instance.name }}</h2>
|
||||
<button ion-button block margin-top (click)="selfEnrolClicked(instance.id)">{{ 'core.courses.enrolme' | translate }}</button>
|
||||
</ion-item>
|
||||
</div>
|
||||
<ion-item text-wrap *ngIf="!isEnrolled && paypalEnabled" detail-none>
|
||||
<p class="item-heading">{{ 'core.courses.paypalaccepted' | translate }}</p>
|
||||
<h2>{{ 'core.courses.paypalaccepted' | translate }}</h2>
|
||||
<p>{{ 'core.paymentinstant' | translate }}</p>
|
||||
<button ion-button block margin-top (click)="paypalEnrol()">{{ 'core.courses.sendpaymentbutton' | translate }}</button>
|
||||
</ion-item>
|
||||
|
|
|
@ -21,14 +21,14 @@ import { CoreSitesProvider } from '../../../../providers/sites';
|
|||
import { CoreDomUtilsProvider } from '../../../../providers/utils/dom';
|
||||
import { CoreTextUtilsProvider } from '../../../../providers/utils/text';
|
||||
import { CoreCoursesProvider } from '../../providers/courses';
|
||||
import { CoreCoursesDelegate } from '../../providers/delegate';
|
||||
import { CoreCourseOptionsDelegate } from '../../../course/providers/options-delegate';
|
||||
import { CoreCourseProvider } from '../../../course/providers/course';
|
||||
import { CoreCourseHelperProvider } from '../../../course/providers/helper';
|
||||
|
||||
/**
|
||||
* Page that allows "previewing" a course and enrolling in it if enabled and not enrolled.
|
||||
*/
|
||||
@IonicPage({segment: "core-courses-course-preview"})
|
||||
@IonicPage({ segment: 'core-courses-course-preview' })
|
||||
@Component({
|
||||
selector: 'page-core-courses-course-preview',
|
||||
templateUrl: 'course-preview.html',
|
||||
|
@ -36,7 +36,7 @@ import { CoreCourseHelperProvider } from '../../../course/providers/helper';
|
|||
export class CoreCoursesCoursePreviewPage implements OnDestroy {
|
||||
course: any;
|
||||
isEnrolled: boolean;
|
||||
handlersShouldBeShown: boolean = true;
|
||||
handlersShouldBeShown = true;
|
||||
handlersLoaded: boolean;
|
||||
component = 'CoreCoursesCoursePreview';
|
||||
selfEnrolInstances: any[] = [];
|
||||
|
@ -47,7 +47,7 @@ export class CoreCoursesCoursePreviewPage implements OnDestroy {
|
|||
};
|
||||
|
||||
protected guestWSAvailable: boolean;
|
||||
protected isGuestEnabled: boolean = false;
|
||||
protected isGuestEnabled = false;
|
||||
protected guestInstanceId: number;
|
||||
protected enrollmentMethods: any[];
|
||||
protected waitStart = 0;
|
||||
|
@ -65,7 +65,7 @@ export class CoreCoursesCoursePreviewPage implements OnDestroy {
|
|||
private domUtils: CoreDomUtilsProvider, private textUtils: CoreTextUtilsProvider, appProvider: CoreAppProvider,
|
||||
private coursesProvider: CoreCoursesProvider, private platform: Platform, private modalCtrl: ModalController,
|
||||
private translate: TranslateService, private eventsProvider: CoreEventsProvider,
|
||||
private coursesDelegate: CoreCoursesDelegate, private courseHelper: CoreCourseHelperProvider,
|
||||
private courseOptionsDelegate: CoreCourseOptionsDelegate, private courseHelper: CoreCourseHelperProvider,
|
||||
private courseProvider: CoreCourseProvider) {
|
||||
this.course = navParams.get('course');
|
||||
this.isMobile = appProvider.isMobile();
|
||||
|
@ -82,7 +82,7 @@ export class CoreCoursesCoursePreviewPage implements OnDestroy {
|
|||
/**
|
||||
* View loaded.
|
||||
*/
|
||||
ionViewDidLoad() {
|
||||
ionViewDidLoad(): void {
|
||||
const currentSite = this.sitesProvider.getCurrentSite(),
|
||||
currentSiteUrl = currentSite && currentSite.getURL();
|
||||
|
||||
|
@ -107,7 +107,7 @@ export class CoreCoursesCoursePreviewPage implements OnDestroy {
|
|||
|
||||
if (icon == 'spinner') {
|
||||
// Course is being downloaded. Get the download promise.
|
||||
let promise = this.courseHelper.getCourseDownloadPromise(this.course.id);
|
||||
const promise = this.courseHelper.getCourseDownloadPromise(this.course.id);
|
||||
if (promise) {
|
||||
// There is a download promise. If it fails, show an error.
|
||||
promise.catch((error) => {
|
||||
|
@ -127,7 +127,7 @@ export class CoreCoursesCoursePreviewPage implements OnDestroy {
|
|||
/**
|
||||
* Page destroyed.
|
||||
*/
|
||||
ngOnDestroy() {
|
||||
ngOnDestroy(): void {
|
||||
this.pageDestroyed = true;
|
||||
|
||||
if (this.courseStatusObserver) {
|
||||
|
@ -141,7 +141,7 @@ export class CoreCoursesCoursePreviewPage implements OnDestroy {
|
|||
* @return {Promise<boolean>} Promise resolved if can access as guest, rejected otherwise. Resolve param indicates if
|
||||
* password is required for guest access.
|
||||
*/
|
||||
protected canAccessAsGuest() : Promise<boolean> {
|
||||
protected canAccessAsGuest(): Promise<boolean> {
|
||||
if (!this.isGuestEnabled) {
|
||||
return Promise.reject(null);
|
||||
}
|
||||
|
@ -149,7 +149,7 @@ export class CoreCoursesCoursePreviewPage implements OnDestroy {
|
|||
// Search instance ID of guest enrolment method.
|
||||
this.guestInstanceId = undefined;
|
||||
for (let i = 0; i < this.enrollmentMethods.length; i++) {
|
||||
let method = this.enrollmentMethods[i];
|
||||
const method = this.enrollmentMethods[i];
|
||||
if (method.type == 'guest') {
|
||||
this.guestInstanceId = method.id;
|
||||
break;
|
||||
|
@ -162,6 +162,7 @@ export class CoreCoursesCoursePreviewPage implements OnDestroy {
|
|||
// Not active, reject.
|
||||
return Promise.reject(null);
|
||||
}
|
||||
|
||||
return info.passwordrequired;
|
||||
});
|
||||
}
|
||||
|
@ -174,9 +175,10 @@ export class CoreCoursesCoursePreviewPage implements OnDestroy {
|
|||
*
|
||||
* @param {boolean} refresh Whether the user is refreshing the data.
|
||||
*/
|
||||
protected getCourse(refresh?: boolean) : Promise<any> {
|
||||
protected getCourse(refresh?: boolean): Promise<any> {
|
||||
// Get course enrolment methods.
|
||||
this.selfEnrolInstances = [];
|
||||
|
||||
return this.coursesProvider.getCourseEnrolmentMethods(this.course.id).then((methods) => {
|
||||
this.enrollmentMethods = methods;
|
||||
|
||||
|
@ -193,15 +195,18 @@ export class CoreCoursesCoursePreviewPage implements OnDestroy {
|
|||
// Check if user is enrolled in the course.
|
||||
return this.coursesProvider.getUserCourse(this.course.id).then((course) => {
|
||||
this.isEnrolled = true;
|
||||
|
||||
return course;
|
||||
}).catch(() => {
|
||||
// The user is not enrolled in the course. Use getCourses to see if it's an admin/manager and can see the course.
|
||||
this.isEnrolled = false;
|
||||
|
||||
return this.coursesProvider.getCourse(this.course.id);
|
||||
}).then((course) => {
|
||||
// Success retrieving the course, we can assume the user has permissions to view it.
|
||||
this.course.fullname = course.fullname || this.course.fullname;
|
||||
this.course.summary = course.summary || this.course.summary;
|
||||
|
||||
return this.loadCourseHandlers(refresh, false);
|
||||
}).catch(() => {
|
||||
// The user is not an admin/manager. Check if we can provide guest access to the course.
|
||||
|
@ -227,8 +232,8 @@ export class CoreCoursesCoursePreviewPage implements OnDestroy {
|
|||
* @param {boolean} refresh Whether the user is refreshing the data.
|
||||
* @param {boolean} guest Whether it's guest access.
|
||||
*/
|
||||
protected loadCourseHandlers(refresh: boolean, guest: boolean) : Promise<any> {
|
||||
return this.coursesDelegate.getHandlersToDisplay(this.course, refresh, guest, true).then((handlers) => {
|
||||
protected loadCourseHandlers(refresh: boolean, guest: boolean): Promise<any> {
|
||||
return this.courseOptionsDelegate.getHandlersToDisplay(this.course, refresh, guest, true).then((handlers) => {
|
||||
this.course._handlers = handlers;
|
||||
this.handlersShouldBeShown = true;
|
||||
this.handlersLoaded = true;
|
||||
|
@ -238,26 +243,27 @@ export class CoreCoursesCoursePreviewPage implements OnDestroy {
|
|||
/**
|
||||
* Open the course.
|
||||
*/
|
||||
openCourse() {
|
||||
openCourse(): void {
|
||||
if (!this.handlersShouldBeShown) {
|
||||
// Course cannot be opened.
|
||||
return;
|
||||
}
|
||||
|
||||
this.navCtrl.push('CoreCourseSectionPage', {course: this.course});
|
||||
this.navCtrl.push('CoreCourseSectionPage', { course: this.course });
|
||||
}
|
||||
|
||||
/**
|
||||
* Enrol using PayPal.
|
||||
*/
|
||||
paypalEnrol() {
|
||||
paypalEnrol(): void {
|
||||
let window,
|
||||
hasReturnedFromPaypal = false,
|
||||
inAppLoadSubscription,
|
||||
inAppFinishSubscription,
|
||||
inAppExitSubscription,
|
||||
appResumeSubscription,
|
||||
urlLoaded = (event) => {
|
||||
appResumeSubscription;
|
||||
|
||||
const urlLoaded = (event): void => {
|
||||
if (event.url.indexOf(this.paypalReturnUrl) != -1) {
|
||||
hasReturnedFromPaypal = true;
|
||||
} else if (event.url.indexOf(this.courseUrl) != -1 && hasReturnedFromPaypal) {
|
||||
|
@ -266,7 +272,7 @@ export class CoreCoursesCoursePreviewPage implements OnDestroy {
|
|||
window.close();
|
||||
}
|
||||
},
|
||||
inAppClosed = () => {
|
||||
inAppClosed = (): void => {
|
||||
// InAppBrowser closed, refresh data.
|
||||
unsubscribeAll();
|
||||
|
||||
|
@ -276,7 +282,7 @@ export class CoreCoursesCoursePreviewPage implements OnDestroy {
|
|||
this.dataLoaded = false;
|
||||
this.refreshData();
|
||||
},
|
||||
unsubscribeAll = () => {
|
||||
unsubscribeAll = (): void => {
|
||||
inAppLoadSubscription && inAppLoadSubscription.unsubscribe();
|
||||
inAppFinishSubscription && inAppFinishSubscription.unsubscribe();
|
||||
inAppExitSubscription && inAppExitSubscription.unsubscribe();
|
||||
|
@ -315,7 +321,7 @@ export class CoreCoursesCoursePreviewPage implements OnDestroy {
|
|||
*
|
||||
* @param {number} instanceId The instance ID of the enrolment method.
|
||||
*/
|
||||
selfEnrolClicked(instanceId: number) {
|
||||
selfEnrolClicked(instanceId: number): void {
|
||||
this.domUtils.showConfirm(this.translate.instant('core.courses.confirmselfenrol')).then(() => {
|
||||
this.selfEnrolInCourse('', instanceId);
|
||||
}).catch(() => {
|
||||
|
@ -330,8 +336,8 @@ export class CoreCoursesCoursePreviewPage implements OnDestroy {
|
|||
* @param {number} instanceId The instance ID.
|
||||
* @return {Promise<any>} Promise resolved when self enrolled.
|
||||
*/
|
||||
selfEnrolInCourse(password: string, instanceId: number) : Promise<any> {
|
||||
let modal = this.domUtils.showModalLoading('core.loading', true);
|
||||
selfEnrolInCourse(password: string, instanceId: number): Promise<any> {
|
||||
const modal = this.domUtils.showModalLoading('core.loading', true);
|
||||
|
||||
return this.coursesProvider.selfEnrol(this.course.id, password, instanceId).then(() => {
|
||||
// Close modal and refresh data.
|
||||
|
@ -343,7 +349,7 @@ export class CoreCoursesCoursePreviewPage implements OnDestroy {
|
|||
this.refreshData().finally(() => {
|
||||
// My courses have been updated, trigger event.
|
||||
this.eventsProvider.trigger(
|
||||
CoreCoursesProvider.EVENT_MY_COURSES_UPDATED, {}, this.sitesProvider.getCurrentSiteId());
|
||||
CoreCoursesProvider.EVENT_MY_COURSES_UPDATED, {}, this.sitesProvider.getCurrentSiteId());
|
||||
});
|
||||
});
|
||||
}).catch((error) => {
|
||||
|
@ -369,13 +375,13 @@ export class CoreCoursesCoursePreviewPage implements OnDestroy {
|
|||
*
|
||||
* @param {any} [refresher] The refresher if this was triggered by a Pull To Refresh.
|
||||
*/
|
||||
refreshData(refresher?: any) : Promise<any> {
|
||||
let promises = [];
|
||||
refreshData(refresher?: any): Promise<any> {
|
||||
const promises = [];
|
||||
|
||||
promises.push(this.coursesProvider.invalidateUserCourses());
|
||||
promises.push(this.coursesProvider.invalidateCourse(this.course.id));
|
||||
promises.push(this.coursesProvider.invalidateCourseEnrolmentMethods(this.course.id));
|
||||
// promises.push($mmCoursesDelegate.clearAndInvalidateCoursesOptions(course.id));
|
||||
promises.push(this.courseOptionsDelegate.clearAndInvalidateCoursesOptions(this.course.id));
|
||||
if (this.guestInstanceId) {
|
||||
promises.push(this.coursesProvider.invalidateCourseGuestEnrolmentInfo(this.guestInstanceId));
|
||||
}
|
||||
|
@ -393,8 +399,9 @@ export class CoreCoursesCoursePreviewPage implements OnDestroy {
|
|||
* Wait for the user to be enrolled in the course.
|
||||
*
|
||||
* @param {boolean} first If it's the first call (true) or it's a recursive call (false).
|
||||
* @return {Promise<any>} Promise resolved when enrolled or timeout.
|
||||
*/
|
||||
protected waitForEnrolled(first?: boolean) {
|
||||
protected waitForEnrolled(first?: boolean): Promise<any> {
|
||||
if (first) {
|
||||
this.waitStart = Date.now();
|
||||
}
|
||||
|
@ -406,12 +413,12 @@ export class CoreCoursesCoursePreviewPage implements OnDestroy {
|
|||
return this.coursesProvider.getUserCourse(this.course.id);
|
||||
}).catch(() => {
|
||||
// Not enrolled, wait a bit and try again.
|
||||
if (this.pageDestroyed || (Date.now() - this.waitStart > 60000)) {
|
||||
if (this.pageDestroyed || (Date.now() - this.waitStart > 60000)) {
|
||||
// Max time reached or the user left the view, stop.
|
||||
return;
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
return new Promise((resolve, reject): void => {
|
||||
setTimeout(() => {
|
||||
if (!this.pageDestroyed) {
|
||||
// Wait again.
|
||||
|
@ -427,12 +434,12 @@ export class CoreCoursesCoursePreviewPage implements OnDestroy {
|
|||
/**
|
||||
* Prefetch the course.
|
||||
*/
|
||||
prefetchCourse() {
|
||||
prefetchCourse(): void {
|
||||
this.courseHelper.confirmAndPrefetchCourse(this.prefetchCourseData, this.course, undefined, this.course._handlers)
|
||||
.catch((error) => {
|
||||
if (!this.pageDestroyed) {
|
||||
this.domUtils.showErrorModalDefault(error, 'core.course.errordownloadingcourse', true);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue