MOBILE-3109 addon: Add return types to all addons except mod

main
Dani Palou 2019-09-02 11:34:48 +02:00
parent 0c6c1b6383
commit b2497a1dd0
61 changed files with 2607 additions and 634 deletions

View File

@ -1,6 +1,6 @@
<ion-header>
<ion-navbar core-back-button>
<ion-title>{{badge.name}}</ion-title>
<ion-title>{{badge && badge.name}}</ion-title>
</ion-navbar>
</ion-header>
<ion-content>
@ -9,7 +9,7 @@
</ion-refresher>
<core-loading [hideUntil]="badgeLoaded">
<ion-item-group>
<ion-item-group *ngIf="badge">
<ion-item text-wrap class="item-avatar-center">
<img *ngIf="badge.badgeurl" class="avatar" [src]="badge.badgeurl" core-external-content [alt]="badge.name">
<ion-badge color="danger" *ngIf="badge.dateexpire && currentTime >= badge.dateexpire">
@ -30,7 +30,7 @@
</ion-item>
</ion-item-group>
<ion-item-group>
<ion-item-group *ngIf="badge">
<ion-item-divider>
<h2>{{ 'addon.badges.issuerdetails' | translate}}</h2>
</ion-item-divider>
@ -48,7 +48,7 @@
</ion-item>
</ion-item-group>
<ion-item-group>
<ion-item-group *ngIf="badge">
<ion-item-divider>
<h2>{{ 'addon.badges.badgedetails' | translate}}</h2>
</ion-item-divider>
@ -99,7 +99,7 @@
<!-- Criteria (not yet avalaible) -->
</ion-item-group>
<ion-item-group>
<ion-item-group *ngIf="badge">
<ion-item-divider>
<h2>{{ 'addon.badges.issuancedetails' | translate}}</h2>
</ion-item-divider>
@ -120,7 +120,7 @@
</ion-item-group>
<!-- Endorsement -->
<ion-item-group *ngIf="badge.endorsement">
<ion-item-group *ngIf="badge && badge.endorsement">
<ion-item-divider>
<h2>{{ 'addon.badges.bendorsement' | translate}}</h2>
</ion-item-divider>
@ -159,7 +159,7 @@
</ion-item-group>
<!-- Related badges -->
<ion-item-group *ngIf="badge.relatedbadges">
<ion-item-group *ngIf="badge && badge.relatedbadges">
<ion-item-divider>
<h2>{{ 'addon.badges.relatedbages' | translate}}</h2>
</ion-item-divider>
@ -172,7 +172,7 @@
</ion-item-group>
<!-- Competencies alignment -->
<ion-item-group *ngIf="badge.competencies">
<ion-item-group *ngIf="badge && badge.competencies">
<ion-item-divider>
<h2>{{ 'addon.badges.alignment' | translate}}</h2>
</ion-item-divider>

View File

@ -19,7 +19,7 @@ import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { CoreSitesProvider } from '@providers/sites';
import { CoreUserProvider } from '@core/user/providers/user';
import { CoreCoursesProvider } from '@core/courses/providers/courses';
import { AddonBadgesProvider } from '../../providers/badges';
import { AddonBadgesProvider, AddonBadgesUserBadge } from '../../providers/badges';
/**
* Page that displays the list of calendar events.
@ -38,7 +38,7 @@ export class AddonBadgesIssuedBadgePage {
user: any = {};
course: any = {};
badge: any = {};
badge: AddonBadgesUserBadge;
badgeLoaded = false;
currentTime = 0;

View File

@ -14,7 +14,7 @@
import { Component, ViewChild } from '@angular/core';
import { IonicPage, Content, NavParams } from 'ionic-angular';
import { AddonBadgesProvider } from '../../providers/badges';
import { AddonBadgesProvider, AddonBadgesUserBadge } from '../../providers/badges';
import { CoreTimeUtilsProvider } from '@providers/utils/time';
import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { CoreSitesProvider } from '@providers/sites';
@ -36,7 +36,7 @@ export class AddonBadgesUserBadgesPage {
userId: number;
badgesLoaded = false;
badges = [];
badges: AddonBadgesUserBadge[] = [];
currentTime = 0;
badgeHash: string;

View File

@ -15,6 +15,7 @@
import { Injectable } from '@angular/core';
import { CoreLoggerProvider } from '@providers/logger';
import { CoreSitesProvider } from '@providers/sites';
import { CoreWSExternalWarning } from '@providers/ws';
import { CoreSite } from '@classes/site';
/**
@ -70,7 +71,7 @@ export class AddonBadgesProvider {
* @param siteId Site ID. If not defined, current site.
* @return Promise to be resolved when the badges are retrieved.
*/
getUserBadges(courseId: number, userId: number, siteId?: string): Promise<any> {
getUserBadges(courseId: number, userId: number, siteId?: string): Promise<AddonBadgesUserBadge[]> {
this.logger.debug('Get badges for course ' + courseId);
@ -110,3 +111,76 @@ export class AddonBadgesProvider {
});
}
}
/**
* Result of WS core_badges_get_user_badges.
*/
export type AddonBadgesGetUserBadgesResult = {
badges: AddonBadgesUserBadge[]; // List of badges.
warnings?: CoreWSExternalWarning[]; // List of warnings.
};
/**
* Badge data returned by WS core_badges_get_user_badges.
*/
export type AddonBadgesUserBadge = {
id?: number; // Badge id.
name: string; // Badge name.
description: string; // Badge description.
timecreated?: number; // Time created.
timemodified?: number; // Time modified.
usercreated?: number; // User created.
usermodified?: number; // User modified.
issuername: string; // Issuer name.
issuerurl: string; // Issuer URL.
issuercontact: string; // Issuer contact.
expiredate?: number; // Expire date.
expireperiod?: number; // Expire period.
type?: number; // Type.
courseid?: number; // Course id.
message?: string; // Message.
messagesubject?: string; // Message subject.
attachment?: number; // Attachment.
notification?: number; // @since 3.6. Whether to notify when badge is awarded.
nextcron?: number; // @since 3.6. Next cron.
status?: number; // Status.
issuedid?: number; // Issued id.
uniquehash: string; // Unique hash.
dateissued: number; // Date issued.
dateexpire: number; // Date expire.
visible?: number; // Visible.
email?: string; // @since 3.6. User email.
version?: string; // @since 3.6. Version.
language?: string; // @since 3.6. Language.
imageauthorname?: string; // @since 3.6. Name of the image author.
imageauthoremail?: string; // @since 3.6. Email of the image author.
imageauthorurl?: string; // @since 3.6. URL of the image author.
imagecaption?: string; // @since 3.6. Caption of the image.
badgeurl: string; // Badge URL.
endorsement?: { // @since 3.6.
id: number; // Endorsement id.
badgeid: number; // Badge id.
issuername: string; // Endorsement issuer name.
issuerurl: string; // Endorsement issuer URL.
issueremail: string; // Endorsement issuer email.
claimid: string; // Claim URL.
claimcomment: string; // Claim comment.
dateissued: number; // Date issued.
};
alignment: { // @since 3.6. Badge alignments.
id?: number; // Alignment id.
badgeid?: number; // Badge id.
targetName?: string; // Target name.
targetUrl?: string; // Target URL.
targetDescription?: string; // Target description.
targetFramework?: string; // Target framework.
targetCode?: string; // Target code.
}[];
relatedbadges: { // @since 3.6. Related badges.
id: number; // Badge id.
name: string; // Badge name.
version?: string; // Version.
language?: string; // Language.
type?: number; // Type.
}[];
};

View File

@ -16,7 +16,9 @@ import { Component, OnInit, Injector, Optional } from '@angular/core';
import { NavController } from 'ionic-angular';
import { CoreSitesProvider } from '@providers/sites';
import { CoreBlockBaseComponent } from '@core/block/classes/base-block-component';
import { AddonBlockRecentlyAccessedItemsProvider } from '../../providers/recentlyaccesseditems';
import {
AddonBlockRecentlyAccessedItemsProvider, AddonBlockRecentlyAccessedItemsItem
} from '../../providers/recentlyaccesseditems';
import { CoreTextUtilsProvider } from '@providers/utils/text';
import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper';
@ -28,7 +30,7 @@ import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/hel
templateUrl: 'addon-block-recentlyaccesseditems.html'
})
export class AddonBlockRecentlyAccessedItemsComponent extends CoreBlockBaseComponent implements OnInit {
items = [];
items: AddonBlockRecentlyAccessedItemsItem[] = [];
protected fetchContentDefaultError = 'Error getting recently accessed items data.';

View File

@ -42,14 +42,16 @@ export class AddonBlockRecentlyAccessedItemsProvider {
* @param siteId Site ID. If not defined, use current site.
* @return Promise resolved when the info is retrieved.
*/
getRecentItems(siteId?: string): Promise<any[]> {
getRecentItems(siteId?: string): Promise<AddonBlockRecentlyAccessedItemsItem[]> {
return this.sitesProvider.getSite(siteId).then((site) => {
const preSets = {
cacheKey: this.getRecentItemsCacheKey()
};
return site.read('block_recentlyaccesseditems_get_recent_items', undefined, preSets).then((items) => {
return site.read('block_recentlyaccesseditems_get_recent_items', undefined, preSets)
.then((items: AddonBlockRecentlyAccessedItemsItem[]) => {
return items.map((item) => {
const modicon = item.icon && this.domUtils.getHTMLElementAttribute(item.icon, 'src');
item.iconUrl = this.courseProvider.getModuleIconSrc(item.modname, modicon);
@ -72,3 +74,27 @@ export class AddonBlockRecentlyAccessedItemsProvider {
});
}
}
/**
* Result of WS block_recentlyaccesseditems_get_recent_items.
*/
export type AddonBlockRecentlyAccessedItemsItem = {
id: number; // Id.
courseid: number; // Courseid.
cmid: number; // Cmid.
userid: number; // Userid.
modname: string; // Modname.
name: string; // Name.
coursename: string; // Coursename.
timeaccess: number; // Timeaccess.
viewurl: string; // Viewurl.
courseviewurl: string; // Courseviewurl.
icon: string; // Icon.
} & AddonBlockRecentlyAccessedItemsItemCalculatedData;
/**
* Calculated data for recently accessed item.
*/
export type AddonBlockRecentlyAccessedItemsItemCalculatedData = {
iconUrl: string; // Icon URL. Calculated by the app.
};

View File

@ -21,6 +21,7 @@ import { CoreCoursesHelperProvider } from '@core/courses/providers/helper';
import { CoreCourseOptionsDelegate } from '@core/course/providers/options-delegate';
import { CoreBlockBaseComponent } from '@core/block/classes/base-block-component';
import { AddonBlockTimelineProvider } from '../../providers/timeline';
import { AddonCalendarEvent } from '@addon/calendar/providers/calendar';
/**
* Component to render a timeline block.
@ -34,9 +35,9 @@ export class AddonBlockTimelineComponent extends CoreBlockBaseComponent implemen
filter = 'next30days';
currentSite: any;
timeline = {
events: [],
events: <AddonCalendarEvent[]> [],
loaded: false,
canLoadMore: undefined
canLoadMore: <number> undefined
};
timelineCourses = {
courses: [],

View File

@ -15,6 +15,7 @@
import { Injectable } from '@angular/core';
import { CoreSitesProvider } from '@providers/sites';
import { CoreCoursesDashboardProvider } from '@core/courses/providers/dashboard';
import { AddonCalendarEvents, AddonCalendarEventsGroupedByCourse, AddonCalendarEvent } from '@addon/calendar/providers/calendar';
import * as moment from 'moment';
/**
@ -38,7 +39,7 @@ export class AddonBlockTimelineProvider {
* @return Promise resolved when the info is retrieved.
*/
getActionEventsByCourse(courseId: number, afterEventId?: number, siteId?: string):
Promise<{ events: any[], canLoadMore: number }> {
Promise<{ events: AddonCalendarEvent[], canLoadMore: number }> {
return this.sitesProvider.getSite(siteId).then((site) => {
const time = moment().subtract(14, 'days').unix(), // Check two weeks ago.
@ -55,7 +56,9 @@ export class AddonBlockTimelineProvider {
data.aftereventid = afterEventId;
}
return site.read('core_calendar_get_action_events_by_course', data, preSets).then((courseEvents): any => {
return site.read('core_calendar_get_action_events_by_course', data, preSets)
.then((courseEvents: AddonCalendarEvents): any => {
if (courseEvents && courseEvents.events) {
return this.treatCourseEvents(courseEvents, time);
}
@ -82,8 +85,9 @@ export class AddonBlockTimelineProvider {
* @param siteId Site ID. If not defined, use current site.
* @return Promise resolved when the info is retrieved.
*/
getActionEventsByCourses(courseIds: number[], siteId?: string): Promise<{ [s: string]:
{ events: any[], canLoadMore: number } }> {
getActionEventsByCourses(courseIds: number[], siteId?: string): Promise<{ [courseId: string]:
{ events: AddonCalendarEvent[], canLoadMore: number } }> {
return this.sitesProvider.getSite(siteId).then((site) => {
const time = moment().subtract(14, 'days').unix(), // Check two weeks ago.
data = {
@ -95,7 +99,9 @@ export class AddonBlockTimelineProvider {
cacheKey: this.getActionEventsByCoursesCacheKey()
};
return site.read('core_calendar_get_action_events_by_courses', data, preSets).then((events): any => {
return site.read('core_calendar_get_action_events_by_courses', data, preSets)
.then((events: AddonCalendarEventsGroupedByCourse): any => {
if (events && events.groupedbycourse) {
const courseEvents = {};
@ -127,7 +133,9 @@ export class AddonBlockTimelineProvider {
* @param siteId Site ID. If not defined, use current site.
* @return Promise resolved when the info is retrieved.
*/
getActionEventsByTimesort(afterEventId: number, siteId?: string): Promise<{ events: any[], canLoadMore: number }> {
getActionEventsByTimesort(afterEventId: number, siteId?: string):
Promise<{ events: AddonCalendarEvent[], canLoadMore: number }> {
return this.sitesProvider.getSite(siteId).then((site) => {
const time = moment().subtract(14, 'days').unix(), // Check two weeks ago.
data: any = {
@ -144,12 +152,14 @@ export class AddonBlockTimelineProvider {
data.aftereventid = afterEventId;
}
return site.read('core_calendar_get_action_events_by_timesort', data, preSets).then((events): any => {
if (events && events.events) {
const canLoadMore = events.events.length >= data.limitnum ? events.lastid : undefined;
return site.read('core_calendar_get_action_events_by_timesort', data, preSets)
.then((result: AddonCalendarEvents): any => {
if (result && result.events) {
const canLoadMore = result.events.length >= data.limitnum ? result.lastid : undefined;
// Filter events by time in case it uses cache.
events = events.events.filter((element) => {
const events = result.events.filter((element) => {
return element.timesort >= time;
});
@ -236,7 +246,9 @@ export class AddonBlockTimelineProvider {
* @param timeFrom Current time to filter events from.
* @return Object with course events and last loaded event id if more can be loaded.
*/
protected treatCourseEvents(course: any, timeFrom: number): { events: any[], canLoadMore: number } {
protected treatCourseEvents(course: AddonCalendarEvents, timeFrom: number):
{ events: AddonCalendarEvent[], canLoadMore: number } {
const canLoadMore: number =
course.events.length >= AddonBlockTimelineProvider.EVENTS_LIMIT_PER_COURSE ? course.lastid : undefined;

View File

@ -18,7 +18,7 @@ import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { CoreUtilsProvider } from '@providers/utils/utils';
import { CoreSitesProvider } from '@providers/sites';
import { CoreUserProvider } from '@core/user/providers/user';
import { AddonBlogProvider } from '../../providers/blog';
import { AddonBlogProvider, AddonBlogPost } from '../../providers/blog';
import { CoreCommentsProvider } from '@core/comments/providers/comments';
import { CoreTagProvider } from '@core/tag/providers/tag';
@ -48,7 +48,7 @@ export class AddonBlogEntriesComponent implements OnInit {
loaded = false;
canLoadMore = false;
loadMoreError = false;
entries = [];
entries: AddonBlogPostFormatted[] = [];
currentUserId: number;
showMyEntriesToggle = false;
onlyMyEntries = false;
@ -118,7 +118,7 @@ export class AddonBlogEntriesComponent implements OnInit {
const loadPage = this.onlyMyEntries ? this.userPageLoaded : this.pageLoaded;
return this.blogProvider.getEntries(this.filter, loadPage).then((result) => {
const promises = result.entries.map((entry) => {
const promises = result.entries.map((entry: AddonBlogPostFormatted) => {
switch (entry.publishstate) {
case 'draft':
entry.publishTranslated = 'publishtonoone';
@ -237,5 +237,12 @@ export class AddonBlogEntriesComponent implements OnInit {
});
});
}
}
/**
* Blog post with some calculated data.
*/
type AddonBlogPostFormatted = AddonBlogPost & {
publishTranslated?: string; // Calculated in the app. Key of the string to translate the publish state of the post.
user?: any; // Calculated in the app. Data of the user that wrote the post.
};

View File

@ -18,6 +18,8 @@ import { CoreSitesProvider } from '@providers/sites';
import { CoreUtilsProvider } from '@providers/utils/utils';
import { CorePushNotificationsProvider } from '@core/pushnotifications/providers/pushnotifications';
import { CoreSite } from '@classes/site';
import { CoreWSExternalWarning, CoreWSExternalFile } from '@providers/ws';
import { CoreTagItem } from '@core/tag/providers/tag';
/**
* Service to handle blog entries.
@ -68,7 +70,7 @@ export class AddonBlogProvider {
* @param siteId Site ID. If not defined, current site.
* @return Promise to be resolved when the entries are retrieved.
*/
getEntries(filter: any = {}, page: number = 0, siteId?: string): Promise<any> {
getEntries(filter: any = {}, page: number = 0, siteId?: string): Promise<AddonBlogGetEntriesResult> {
return this.sitesProvider.getSite(siteId).then((site) => {
const data = {
filters: this.utils.objectToArrayOfObjects(filter, 'name', 'value'),
@ -105,7 +107,7 @@ export class AddonBlogProvider {
* @param siteId Site ID. If not defined, current site.
* @return Promise to be resolved when done.
*/
logView(filter: any = {}, siteId?: string): Promise<any> {
logView(filter: any = {}, siteId?: string): Promise<AddonBlogViewEntriesResult> {
this.pushNotificationsProvider.logViewListEvent('blog', 'core_blog_view_entries', filter, siteId);
return this.sitesProvider.getSite(siteId).then((site) => {
@ -117,3 +119,48 @@ export class AddonBlogProvider {
});
}
}
/**
* Data returned by blog's post_exporter.
*/
export type AddonBlogPost = {
id: number; // Post/entry id.
module: string; // Where it was published the post (blog, blog_external...).
userid: number; // Post author.
courseid: number; // Course where the post was created.
groupid: number; // Group post was created for.
moduleid: number; // Module id where the post was created (not used anymore).
coursemoduleid: number; // Course module id where the post was created.
subject: string; // Post subject.
summary: string; // Post summary.
summaryformat: number; // Summary format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
content: string; // Post content.
uniquehash: string; // Post unique hash.
rating: number; // Post rating.
format: number; // Post content format.
attachment: string; // Post atachment.
publishstate: string; // Post publish state.
lastmodified: number; // When it was last modified.
created: number; // When it was created.
usermodified: number; // User that updated the post.
summaryfiles: CoreWSExternalFile[]; // Summaryfiles.
attachmentfiles?: CoreWSExternalFile[]; // Attachmentfiles.
tags?: CoreTagItem[]; // @since 3.7. Tags.
};
/**
* Result of WS core_blog_get_entries.
*/
export type AddonBlogGetEntriesResult = {
entries: AddonBlogPost[];
totalentries: number; // The total number of entries found.
warnings?: CoreWSExternalWarning[];
};
/**
* Result of WS core_blog_view_entries.
*/
export type AddonBlogViewEntriesResult = {
status: boolean; // Status: true if success.
warnings?: CoreWSExternalWarning[];
};

View File

@ -21,6 +21,7 @@ import { CoreCourseProvider } from '@core/course/providers/course';
import { CoreCourseHelperProvider } from '@core/course/providers/helper';
import { AddonBlogEntriesComponent } from '../components/entries/entries';
import { AddonBlogProvider } from './blog';
import { CoreWSExternalFile } from '@providers/ws';
/**
* Course nav handler.
@ -100,7 +101,7 @@ export class AddonBlogCourseOptionHandler implements CoreCourseOptionsHandler {
return this.blogProvider.getEntries({courseid: course.id}).then((result) => {
return result.entries.map((entry) => {
let files = [];
let files: CoreWSExternalFile[] = [];
if (entry.attachmentfiles && entry.attachmentfiles.length) {
files = entry.attachmentfiles;

View File

@ -19,7 +19,7 @@ import { CoreSitesProvider } from '@providers/sites';
import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { CoreTimeUtilsProvider } from '@providers/utils/time';
import { CoreUtilsProvider } from '@providers/utils/utils';
import { AddonCalendarProvider } from '../../providers/calendar';
import { AddonCalendarProvider, AddonCalendarWeek } from '../../providers/calendar';
import { AddonCalendarHelperProvider } from '../../providers/helper';
import { AddonCalendarOfflineProvider } from '../../providers/calendar-offline';
import { CoreCoursesProvider } from '@core/courses/providers/courses';
@ -44,7 +44,7 @@ export class AddonCalendarCalendarComponent implements OnInit, OnChanges, OnDest
periodName: string;
weekDays: any[];
weeks: any[];
weeks: AddonCalendarWeek[];
loaded = false;
timeFormat: string;
isCurrentMonth: boolean;

View File

@ -6,7 +6,7 @@
<ng-container *ngFor="let event of filteredEvents">
<a ion-item text-wrap [title]="event.name" (click)="eventClicked(event)" [class.core-split-item-selected]="event.id == eventId" class="addon-calendar-event" [ngClass]="['addon-calendar-eventtype-'+event.eventtype]">
<img *ngIf="event.moduleIcon" src="{{event.moduleIcon}}" item-start class="core-module-icon">
<core-icon *ngIf="event.icon && !event.moduleIcon" [name]="event.icon" item-start></core-icon>
<core-icon *ngIf="event.eventIcon && !event.moduleIcon" [name]="event.eventIcon" item-start></core-icon>
<h2><core-format-text [text]="event.name"></core-format-text></h2>
<p><core-format-text [text]="event.formattedtime"></core-format-text></p>
<ion-note *ngIf="event.offline && !event.deleted" item-end>

View File

@ -17,7 +17,7 @@ import { CoreEventsProvider } from '@providers/events';
import { CoreLocalNotificationsProvider } from '@providers/local-notifications';
import { CoreSitesProvider } from '@providers/sites';
import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { AddonCalendarProvider } from '../../providers/calendar';
import { AddonCalendarProvider, AddonCalendarCalendarEvent } from '../../providers/calendar';
import { AddonCalendarHelperProvider } from '../../providers/helper';
import { AddonCalendarOfflineProvider } from '../../providers/calendar-offline';
import { CoreCoursesProvider } from '@core/courses/providers/courses';
@ -43,8 +43,8 @@ export class AddonCalendarUpcomingEventsComponent implements OnInit, OnChanges,
protected categoriesRetrieved = false;
protected categories = {};
protected currentSiteId: string;
protected events = []; // Events (both online and offline).
protected onlineEvents = [];
protected events: AddonCalendarCalendarEvent[] = []; // Events (both online and offline).
protected onlineEvents: AddonCalendarCalendarEvent[] = [];
protected offlineEvents = []; // Offline events.
protected deletedEvents = []; // Events deleted in offline.
protected lookAhead: number;

View File

@ -50,7 +50,7 @@
<ng-container *ngFor="let event of filteredEvents">
<ion-item text-wrap [title]="event.name" (click)="gotoEvent(event.id)" [class.item-dimmed]="event.ispast" class="addon-calendar-event" [ngClass]="['addon-calendar-eventtype-'+event.eventtype]">
<img *ngIf="event.moduleIcon" src="{{event.moduleIcon}}" item-start class="core-module-icon">
<core-icon *ngIf="event.icon && !event.moduleIcon" [name]="event.icon" item-start></core-icon>
<core-icon *ngIf="event.eventIcon && !event.moduleIcon" [name]="event.eventIcon" item-start></core-icon>
<h2><core-format-text [text]="event.name"></core-format-text></h2>
<p><core-format-text [text]="event.formattedtime"></core-format-text></p>
<ion-note *ngIf="event.offline && !event.deleted" item-end>

View File

@ -20,7 +20,7 @@ import { CoreLocalNotificationsProvider } from '@providers/local-notifications';
import { CoreSitesProvider } from '@providers/sites';
import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { CoreTimeUtilsProvider } from '@providers/utils/time';
import { AddonCalendarProvider } from '../../providers/calendar';
import { AddonCalendarProvider, AddonCalendarCalendarEvent } from '../../providers/calendar';
import { AddonCalendarOfflineProvider } from '../../providers/calendar-offline';
import { AddonCalendarHelperProvider } from '../../providers/helper';
import { AddonCalendarSyncProvider } from '../../providers/calendar-sync';
@ -45,7 +45,7 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy {
protected day: number;
protected categories = {};
protected events = []; // Events (both online and offline).
protected onlineEvents = [];
protected onlineEvents: AddonCalendarCalendarEvent[] = [];
protected offlineEvents = {}; // Offline events.
protected offlineEditedEventsIds = []; // IDs of events edited in offline.
protected deletedEvents = []; // Events deleted in offline.
@ -287,7 +287,7 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy {
return this.calendarProvider.getDayEvents(this.year, this.month, this.day).catch((error) => {
if (!this.appProvider.isOnline()) {
// Allow navigating to non-cached days in offline (behave as if using emergency cache).
return Promise.resolve({ events: [] });
return Promise.resolve({ events: <AddonCalendarCalendarEvent[]> [] });
} else {
return Promise.reject(error);
}

View File

@ -134,7 +134,7 @@
<div *ngIf="event && event.repeatid" text-wrap radio-group [formControlName]="'repeateditall'" class="addon-calendar-radio-container">
<ion-item class="addon-calendar-radio-title"><h2>{{ 'addon.calendar.repeatedevents' | translate }}</h2></ion-item>
<ion-item>
<ion-label>{{ 'addon.calendar.repeateditall' | translate:{$a: event.othereventscount} }}</ion-label>
<ion-label>{{ 'addon.calendar.repeateditall' | translate:{$a: otherEventsCount} }}</ion-label>
<ion-radio [value]="1"></ion-radio>
</ion-item>
<ion-item>

View File

@ -27,7 +27,7 @@ import { CoreUtilsProvider } from '@providers/utils/utils';
import { CoreCoursesProvider } from '@core/courses/providers/courses';
import { CoreSplitViewComponent } from '@components/split-view/split-view';
import { CoreRichTextEditorComponent } from '@components/rich-text-editor/rich-text-editor.ts';
import { AddonCalendarProvider } from '../../providers/calendar';
import { AddonCalendarProvider, AddonCalendarGetAccessInfoResult, AddonCalendarEvent } from '../../providers/calendar';
import { AddonCalendarOfflineProvider } from '../../providers/calendar-offline';
import { AddonCalendarHelperProvider } from '../../providers/helper';
import { AddonCalendarSyncProvider } from '../../providers/calendar-sync';
@ -58,7 +58,8 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy {
courseGroupSet = false;
advanced = false;
errors: any;
event: any; // The event object (when editing an event).
event: AddonCalendarEvent; // The event object (when editing an event).
otherEventsCount: number;
// Form variables.
eventForm: FormGroup;
@ -70,7 +71,7 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy {
protected courseId: number;
protected originalData: any;
protected currentSite: CoreSite;
protected types: any; // Object with the supported types.
protected types: {[name: string]: boolean}; // Object with the supported types.
protected showAll: boolean;
protected isDestroyed = false;
protected error = false;
@ -152,7 +153,7 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy {
* @return Promise resolved when done.
*/
protected fetchData(refresh?: boolean): Promise<any> {
let accessInfo;
let accessInfo: AddonCalendarGetAccessInfoResult;
this.error = false;
@ -197,7 +198,7 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy {
promises.push(this.calendarProvider.getEventById(this.eventId).then((event) => {
this.event = event;
if (event && event.repeatid) {
event.othereventscount = event.eventcount ? event.eventcount - 1 : '';
this.otherEventsCount = event.eventcount ? event.eventcount - 1 : 0;
}
return event;
@ -489,7 +490,7 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy {
// Send the data.
const modal = this.domUtils.showModalLoading('core.sending', true);
let event;
let event: AddonCalendarEvent;
this.calendarProvider.submitEvent(this.eventId, data).then((result) => {
event = result.event;
@ -497,7 +498,7 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy {
if (result.sent) {
// Event created or edited, invalidate right days & months.
const numberOfRepetitions = formData.repeat ? formData.repeats :
(data.repeateditall && this.event.othereventscount ? this.event.othereventscount + 1 : 1);
(data.repeateditall && this.otherEventsCount ? this.otherEventsCount + 1 : 1);
return this.calendarHelper.refreshAfterChangeEvent(result.event, numberOfRepetitions).catch(() => {
// Ignore errors.

View File

@ -2,7 +2,7 @@
<ion-navbar core-back-button>
<ion-title>
<img *ngIf="event && event.moduleIcon" src="{{event.moduleIcon}}" alt="" role="presentation" class="core-module-icon">
<core-icon *ngIf="event && event.icon && !event.moduleIcon" [name]="event.icon" item-start></core-icon>
<core-icon *ngIf="event && event.eventIcon && !event.moduleIcon" [name]="event.eventIcon" item-start></core-icon>
<core-format-text *ngIf="event" [text]="event.name"></core-format-text>
</ion-title>
<ion-buttons end>
@ -32,7 +32,7 @@
<ion-card-content *ngIf="event">
<ion-item text-wrap *ngIf="isSplitViewOn">
<img *ngIf="event.moduleIcon" src="{{event.moduleIcon}}" item-start alt="" role="presentation" class="core-module-icon">
<core-icon *ngIf="event.icon && !event.moduleIcon" [name]="event.icon" item-start></core-icon>
<core-icon *ngIf="event.eventIcon && !event.moduleIcon" [name]="event.eventIcon" item-start></core-icon>
<h2>{{ 'addon.calendar.eventname' | translate }}</h2>
<p><core-format-text [text]="event.name"></core-format-text></p>
<ion-note item-end *ngIf="event.deleted">

View File

@ -34,7 +34,7 @@
</ion-item-divider>
<a ion-item text-wrap [title]="event.name" (click)="gotoEvent(event.id)" [class.core-split-item-selected]="event.id == eventId" class="addon-calendar-event" [ngClass]="['addon-calendar-eventtype-'+event.eventtype]">
<img *ngIf="event.moduleIcon" src="{{event.moduleIcon}}" item-start class="core-module-icon">
<core-icon *ngIf="event.icon && !event.moduleIcon" [name]="event.icon" item-start></core-icon>
<core-icon *ngIf="event.eventIcon && !event.moduleIcon" [name]="event.eventIcon" item-start></core-icon>
<h2><core-format-text [text]="event.name"></core-format-text></h2>
<p>
{{ event.timestart * 1000 | coreFormatDate: "strftimetime" }}

View File

@ -14,7 +14,7 @@
import { Component, ViewChild, OnDestroy, NgZone } from '@angular/core';
import { IonicPage, Content, NavParams, NavController } from 'ionic-angular';
import { AddonCalendarProvider } from '../../providers/calendar';
import { AddonCalendarProvider, AddonCalendarGetEventsEvent } from '../../providers/calendar';
import { AddonCalendarOfflineProvider } from '../../providers/calendar-offline';
import { AddonCalendarHelperProvider } from '../../providers/helper';
import { AddonCalendarSyncProvider } from '../../providers/calendar-sync';
@ -62,7 +62,7 @@ export class AddonCalendarListPage implements OnDestroy {
protected manualSyncObserver: any;
protected onlineObserver: any;
protected currentSiteId: string;
protected onlineEvents = [];
protected onlineEvents: AddonCalendarGetEventsEvent[] = [];
protected offlineEvents = [];
protected deletedEvents = [];
@ -70,7 +70,7 @@ export class AddonCalendarListPage implements OnDestroy {
eventsLoaded = false;
events = []; // Events (both online and offline).
notificationsEnabled = false;
filteredEvents = [];
filteredEvents: AddonCalendarGetEventsEvent[] = [];
canLoadMore = false;
loadMoreError = false;
courseId: number;
@ -402,7 +402,7 @@ export class AddonCalendarListPage implements OnDestroy {
*
* @return Filtered events.
*/
protected getFilteredEvents(): any[] {
protected getFilteredEvents(): AddonCalendarGetEventsEvent[] {
if (!this.courseId) {
// No filter, display everything.
return this.events;
@ -581,7 +581,7 @@ export class AddonCalendarListPage implements OnDestroy {
* @param event Event info.
* @return If date has changed and should be shown.
*/
protected endsSameDay(event: any): boolean {
protected endsSameDay(event: AddonCalendarGetEventsEvent): boolean {
if (!event.timeduration) {
// No duration.
return true;

View File

@ -31,6 +31,7 @@ import { SQLiteDB } from '@classes/sqlitedb';
import { AddonCalendarOfflineProvider } from './calendar-offline';
import { CoreUserProvider } from '@core/user/providers/user';
import { TranslateService } from '@ngx-translate/core';
import { CoreWSExternalWarning, CoreWSDate } from '@providers/ws';
import * as moment from 'moment';
/**
@ -489,7 +490,7 @@ export class AddonCalendarProvider {
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved when done.
*/
deleteEventOnline(eventId: number, deleteAll?: boolean, siteId?: string): Promise<any> {
deleteEventOnline(eventId: number, deleteAll?: boolean, siteId?: string): Promise<null> {
return this.sitesProvider.getSite(siteId).then((site) => {
const params = {
@ -535,22 +536,6 @@ export class AddonCalendarProvider {
});
}
/**
* Check if event ends the same day or not.
*
* @param event Event info.
* @return If the .
*/
endsSameDay(event: any): boolean {
if (!event.timeduration) {
// No duration.
return true;
}
// Check if day has changed.
return moment(event.timestart * 1000).isSame((event.timestart + event.timeduration) * 1000, 'day');
}
/**
* Format event time. Similar to calendar_format_event_time.
*
@ -562,8 +547,8 @@ export class AddonCalendarProvider {
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved with the formatted event time.
*/
formatEventTime(event: any, format: string, useCommonWords: boolean = true, seenDay?: number, showTime: number = 0,
siteId?: string): Promise<string> {
formatEventTime(event: AddonCalendarAnyEvent, format: string, useCommonWords: boolean = true, seenDay?: number,
showTime: number = 0, siteId?: string): Promise<string> {
const start = event.timestart * 1000,
end = (event.timestart + event.timeduration) * 1000;
@ -635,7 +620,7 @@ export class AddonCalendarProvider {
* @return Promise resolved with object with access information.
* @since 3.7
*/
getAccessInformation(courseId?: number, siteId?: string): Promise<any> {
getAccessInformation(courseId?: number, siteId?: string): Promise<AddonCalendarGetAccessInfoResult> {
return this.sitesProvider.getSite(siteId).then((site) => {
const params: any = {},
preSets = {
@ -680,7 +665,7 @@ export class AddonCalendarProvider {
* @return Promise resolved with an object indicating the types.
* @since 3.7
*/
getAllowedEventTypes(courseId?: number, siteId?: string): Promise<any> {
getAllowedEventTypes(courseId?: number, siteId?: string): Promise<{[name: string]: boolean}> {
return this.sitesProvider.getSite(siteId).then((site) => {
const params: any = {},
preSets = {
@ -691,7 +676,8 @@ export class AddonCalendarProvider {
params.courseid = courseId;
}
return site.read('core_calendar_get_allowed_event_types', params, preSets).then((response) => {
return site.read('core_calendar_get_allowed_event_types', params, preSets)
.then((response: AddonCalendarGetAllowedEventTypesResult) => {
// Convert the array to an object.
const result = {};
@ -812,11 +798,10 @@ export class AddonCalendarProvider {
* Get a calendar event. If the server request fails and data is not cached, try to get it from local DB.
*
* @param id Event ID.
* @param refresh True when we should update the event data.
* @param siteId ID of the site. If not defined, use current site.
* @return Promise resolved when the event data is retrieved.
*/
getEvent(id: number, siteId?: string): Promise<any> {
getEvent(id: number, siteId?: string): Promise<AddonCalendarGetEventsEvent> {
return this.sitesProvider.getSite(siteId).then((site) => {
const preSets = {
cacheKey: this.getEventCacheKey(id),
@ -834,7 +819,8 @@ export class AddonCalendarProvider {
}
};
return site.read('core_calendar_get_calendar_events', data, preSets).then((response) => {
return site.read('core_calendar_get_calendar_events', data, preSets)
.then((response: AddonCalendarGetEventsResult) => {
// The WebService returns all category events. Check the response to search for the event we want.
const event = response.events.find((e) => { return e.id == id; });
@ -849,12 +835,11 @@ export class AddonCalendarProvider {
* Get a calendar event by ID. This function returns more data than getEvent, but it isn't available in all Moodles.
*
* @param id Event ID.
* @param refresh True when we should update the event data.
* @param siteId ID of the site. If not defined, use current site.
* @return Promise resolved when the event data is retrieved.
* @since 3.4
*/
getEventById(id: number, siteId?: string): Promise<any> {
getEventById(id: number, siteId?: string): Promise<AddonCalendarEvent> {
return this.sitesProvider.getSite(siteId).then((site) => {
const preSets = {
cacheKey: this.getEventCacheKey(id),
@ -864,7 +849,8 @@ export class AddonCalendarProvider {
eventid: id
};
return site.read('core_calendar_get_calendar_event_by_id', data, preSets).then((response) => {
return site.read('core_calendar_get_calendar_event_by_id', data, preSets)
.then((response: AddonCalendarGetEventByIdResult) => {
return response.event;
}).catch((error) => {
return this.getEventFromLocalDb(id).catch(() => {
@ -918,7 +904,7 @@ export class AddonCalendarProvider {
* @param siteId ID of the site the event belongs to. If not defined, use current site.
* @return Promise resolved when the notification is updated.
*/
addEventReminder(event: any, time: number, siteId?: string): Promise<any> {
addEventReminder(event: AddonCalendarAnyEvent, time: number, siteId?: string): Promise<any> {
return this.sitesProvider.getSite(siteId).then((site) => {
const reminder = {
eventid: event.id,
@ -976,7 +962,7 @@ export class AddonCalendarProvider {
* @return Promise resolved with the response.
*/
getDayEvents(year: number, month: number, day: number, courseId?: number, categoryId?: number, ignoreCache?: boolean,
siteId?: string): Promise<any> {
siteId?: string): Promise<AddonCalendarCalendarDay> {
return this.sitesProvider.getSite(siteId).then((site) => {
@ -1003,7 +989,7 @@ export class AddonCalendarProvider {
preSets.emergencyCache = false;
}
return site.read('core_calendar_get_calendar_day_view', data, preSets).then((response) => {
return site.read('core_calendar_get_calendar_day_view', data, preSets).then((response: AddonCalendarCalendarDay) => {
this.storeEventsInLocalDB(response.events, siteId);
return response;
@ -1071,10 +1057,10 @@ export class AddonCalendarProvider {
* @param daysToStart Number of days from now to start getting events.
* @param daysInterval Number of days between timestart and timeend.
* @param siteId Site to get the events from. If not defined, use current site.
* @return Promise to be resolved when the participants are retrieved.
* @return Promise to be resolved when the events are retrieved.
*/
getEventsList(initialTime?: number, daysToStart: number = 0, daysInterval: number = AddonCalendarProvider.DAYS_INTERVAL,
siteId?: string): Promise<any[]> {
siteId?: string): Promise<AddonCalendarGetEventsEvent[]> {
initialTime = initialTime || this.timeUtils.timestamp();
@ -1122,7 +1108,9 @@ export class AddonCalendarProvider {
updateFrequency: CoreSite.FREQUENCY_SOMETIMES
};
return site.read('core_calendar_get_calendar_events', data, preSets).then((response) => {
return site.read('core_calendar_get_calendar_events', data, preSets)
.then((response: AddonCalendarGetEventsResult) => {
if (!this.canViewMonthInSite(site)) {
// Store events only in 3.1-3.3. In 3.4+ we'll use the new WS that return more info.
this.storeEventsInLocalDB(response.events, siteId);
@ -1178,7 +1166,7 @@ export class AddonCalendarProvider {
* @return Promise resolved with the response.
*/
getMonthlyEvents(year: number, month: number, courseId?: number, categoryId?: number, ignoreCache?: boolean, siteId?: string)
: Promise<any> {
: Promise<AddonCalendarMonth> {
return this.sitesProvider.getSite(siteId).then((site) => {
@ -1210,7 +1198,9 @@ export class AddonCalendarProvider {
preSets.emergencyCache = false;
}
return site.read('core_calendar_get_calendar_monthly_view', data, preSets).then((response) => {
return site.read('core_calendar_get_calendar_monthly_view', data, preSets)
.then((response: AddonCalendarMonth) => {
response.weeks.forEach((week) => {
week.days.forEach((day) => {
this.storeEventsInLocalDB(day.events, siteId);
@ -1270,7 +1260,8 @@ export class AddonCalendarProvider {
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved with the response.
*/
getUpcomingEvents(courseId?: number, categoryId?: number, ignoreCache?: boolean, siteId?: string): Promise<any> {
getUpcomingEvents(courseId?: number, categoryId?: number, ignoreCache?: boolean, siteId?: string)
: Promise<AddonCalendarUpcoming> {
return this.sitesProvider.getSite(siteId).then((site) => {
@ -1293,7 +1284,7 @@ export class AddonCalendarProvider {
preSets.emergencyCache = false;
}
return site.read('core_calendar_get_calendar_upcoming_view', data, preSets).then((response) => {
return site.read('core_calendar_get_calendar_upcoming_view', data, preSets).then((response: AddonCalendarUpcoming) => {
this.storeEventsInLocalDB(response.events, siteId);
return response;
@ -1604,11 +1595,14 @@ export class AddonCalendarProvider {
* If local notification plugin is not enabled, resolve the promise.
*
* @param event Event to schedule.
* @param reminderId The reminder ID.
* @param time Notification setting time (in minutes). E.g. 10 means "notificate 10 minutes before start".
* @param siteId Site ID the event belongs to. If not defined, use current site.
* @return Promise resolved when the notification is scheduled.
*/
protected scheduleEventNotification(event: any, reminderId: number, time: number, siteId?: string): Promise<void> {
protected scheduleEventNotification(event: AddonCalendarAnyEvent, reminderId: number, time: number, siteId?: string)
: Promise<void> {
if (this.localNotificationsProvider.isAvailable()) {
siteId = siteId || this.sitesProvider.getCurrentSiteId();
@ -1672,7 +1666,7 @@ export class AddonCalendarProvider {
* @param siteId ID of the site the events belong to. If not defined, use current site.
* @return Promise resolved when all the notifications have been scheduled.
*/
scheduleEventsNotifications(events: any[], siteId?: string): Promise<any[]> {
scheduleEventsNotifications(events: AddonCalendarAnyEvent[], siteId?: string): Promise<any[]> {
if (this.localNotificationsProvider.isAvailable()) {
siteId = siteId || this.sitesProvider.getCurrentSiteId();
@ -1803,11 +1797,10 @@ export class AddonCalendarProvider {
* @param timeCreated The time the event was created. Only if modifying a new offline event.
* @param forceOffline True to always save it in offline.
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved with the event and a boolean indicating if data was
* sent to server or stored in offline.
* @return Promise resolved with the event and a boolean indicating if data was sent to server or stored in offline.
*/
submitEvent(eventId: number, formData: any, timeCreated?: number, forceOffline?: boolean, siteId?: string):
Promise<{sent: boolean, event: any}> {
Promise<{sent: boolean, event: AddonCalendarEvent}> {
siteId = siteId || this.sitesProvider.getCurrentSiteId();
@ -1847,7 +1840,7 @@ export class AddonCalendarProvider {
* @param siteId Site ID. If not provided, current site.
* @return Promise resolved when done.
*/
submitEventOnline(eventId: number, formData: any, siteId?: string): Promise<any> {
submitEventOnline(eventId: number, formData: any, siteId?: string): Promise<AddonCalendarEvent> {
return this.sitesProvider.getSite(siteId).then((site) => {
// Add data that is "hidden" in web.
formData.id = eventId || 0;
@ -1865,10 +1858,12 @@ export class AddonCalendarProvider {
formdata: this.utils.objectToGetParams(formData)
};
return site.write('core_calendar_submit_create_update_form', params).then((result) => {
return site.write('core_calendar_submit_create_update_form', params)
.then((result: AddonCalendarSubmitCreateUpdateFormResult): AddonCalendarEvent => {
if (result.validationerror) {
// Simulate a WS error.
return Promise.reject({
return <any> Promise.reject({
message: this.translate.instant('core.invalidformdata'),
errorcode: 'validationerror'
});
@ -1879,3 +1874,337 @@ export class AddonCalendarProvider {
});
}
}
/**
* Data returned by calendar's events_exporter.
*/
export type AddonCalendarEvents = {
events: AddonCalendarEvent[]; // Events.
firstid: number; // Firstid.
lastid: number; // Lastid.
};
/**
* Data returned by calendar's events_grouped_by_course_exporter.
*/
export type AddonCalendarEventsGroupedByCourse = {
groupedbycourse: AddonCalendarEventsSameCourse[]; // Groupped by course.
};
/**
* Data returned by calendar's events_same_course_exporter.
*/
export type AddonCalendarEventsSameCourse = AddonCalendarEvents & {
courseid: number; // Courseid.
};
/**
* Data returned by calendar's event_exporter_base.
*/
export type AddonCalendarEventBase = {
id: number; // Id.
name: string; // Name.
description?: string; // Description.
descriptionformat: number; // Description format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
location?: string; // Location.
categoryid?: number; // Categoryid.
groupid?: number; // Groupid.
userid?: number; // Userid.
repeatid?: number; // Repeatid.
eventcount?: number; // Eventcount.
modulename?: string; // Modulename.
instance?: number; // Instance.
eventtype: string; // Eventtype.
timestart: number; // Timestart.
timeduration: number; // Timeduration.
timesort: number; // Timesort.
visible: number; // Visible.
timemodified: number; // Timemodified.
icon: {
key: string; // Key.
component: string; // Component.
alttext: string; // Alttext.
};
category?: {
id: number; // Id.
name: string; // Name.
idnumber: string; // Idnumber.
description?: string; // Description.
parent: number; // Parent.
coursecount: number; // Coursecount.
visible: number; // Visible.
timemodified: number; // Timemodified.
depth: number; // Depth.
nestedname: string; // Nestedname.
url: string; // Url.
};
course?: {
id: number; // Id.
fullname: string; // Fullname.
shortname: string; // Shortname.
idnumber: string; // Idnumber.
summary: string; // Summary.
summaryformat: number; // Summary format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
startdate: number; // Startdate.
enddate: number; // Enddate.
visible: boolean; // Visible.
fullnamedisplay: string; // Fullnamedisplay.
viewurl: string; // Viewurl.
courseimage: string; // Courseimage.
progress?: number; // Progress.
hasprogress: boolean; // Hasprogress.
isfavourite: boolean; // Isfavourite.
hidden: boolean; // Hidden.
timeaccess?: number; // Timeaccess.
showshortname: boolean; // Showshortname.
coursecategory: string; // Coursecategory.
};
subscription?: {
displayeventsource: boolean; // Displayeventsource.
subscriptionname?: string; // Subscriptionname.
subscriptionurl?: string; // Subscriptionurl.
};
canedit: boolean; // Canedit.
candelete: boolean; // Candelete.
deleteurl: string; // Deleteurl.
editurl: string; // Editurl.
viewurl: string; // Viewurl.
formattedtime: string; // Formattedtime.
isactionevent: boolean; // Isactionevent.
iscourseevent: boolean; // Iscourseevent.
iscategoryevent: boolean; // Iscategoryevent.
groupname?: string; // Groupname.
normalisedeventtype: string; // Normalisedeventtype.
normalisedeventtypetext: string; // Normalisedeventtypetext.
};
/**
* Data returned by calendar's event_exporter. Don't confuse it with AddonCalendarCalendarEvent.
*/
export type AddonCalendarEvent = AddonCalendarEventBase & {
url: string; // Url.
action?: {
name: string; // Name.
url: string; // Url.
itemcount: number; // Itemcount.
actionable: boolean; // Actionable.
showitemcount: boolean; // Showitemcount.
};
};
/**
* Data returned by calendar's calendar_event_exporter. Don't confuse it with AddonCalendarEvent.
*/
export type AddonCalendarCalendarEvent = AddonCalendarEventBase & {
url: string; // Url.
islastday: boolean; // Islastday.
popupname: string; // Popupname.
mindaytimestamp?: number; // Mindaytimestamp.
mindayerror?: string; // Mindayerror.
maxdaytimestamp?: number; // Maxdaytimestamp.
maxdayerror?: string; // Maxdayerror.
draggable: boolean; // Draggable.
} & AddonCalendarCalendarEventCalculatedData;
/**
* Any of the possible types of events.
*/
export type AddonCalendarAnyEvent = AddonCalendarGetEventsEvent | AddonCalendarEvent | AddonCalendarCalendarEvent;
/**
* Data returned by calendar's calendar_day_exporter. Don't confuse it with AddonCalendarDay.
*/
export type AddonCalendarCalendarDay = {
events: AddonCalendarCalendarEvent[]; // Events.
defaulteventcontext: number; // Defaulteventcontext.
filter_selector: string; // Filter_selector.
courseid: number; // Courseid.
categoryid?: number; // Categoryid.
neweventtimestamp: number; // Neweventtimestamp.
date: CoreWSDate;
periodname: string; // Periodname.
previousperiod: CoreWSDate;
previousperiodlink: string; // Previousperiodlink.
previousperiodname: string; // Previousperiodname.
nextperiod: CoreWSDate;
nextperiodname: string; // Nextperiodname.
nextperiodlink: string; // Nextperiodlink.
larrow: string; // Larrow.
rarrow: string; // Rarrow.
};
/**
* Data returned by calendar's month_exporter.
*/
export type AddonCalendarMonth = {
url: string; // Url.
courseid: number; // Courseid.
categoryid?: number; // Categoryid.
filter_selector?: string; // Filter_selector.
weeks: AddonCalendarWeek[]; // Weeks.
daynames: AddonCalendarDayName[]; // Daynames.
view: string; // View.
date: CoreWSDate;
periodname: string; // Periodname.
includenavigation: boolean; // Includenavigation.
initialeventsloaded: boolean; // Initialeventsloaded.
previousperiod: CoreWSDate;
previousperiodlink: string; // Previousperiodlink.
previousperiodname: string; // Previousperiodname.
nextperiod: CoreWSDate;
nextperiodname: string; // Nextperiodname.
nextperiodlink: string; // Nextperiodlink.
larrow: string; // Larrow.
rarrow: string; // Rarrow.
defaulteventcontext: number; // Defaulteventcontext.
};
/**
* Data returned by calendar's week_exporter.
*/
export type AddonCalendarWeek = {
prepadding: number[]; // Prepadding.
postpadding: number[]; // Postpadding.
days: AddonCalendarWeekDay[]; // Days.
};
/**
* Data returned by calendar's week_day_exporter.
*/
export type AddonCalendarWeekDay = AddonCalendarDay & {
istoday: boolean; // Istoday.
isweekend: boolean; // Isweekend.
popovertitle: string; // Popovertitle.
ispast?: boolean; // Calculated in the app. Whether the day is in the past.
filteredEvents?: AddonCalendarCalendarEvent[]; // Calculated in the app. Filtered events.
};
/**
* Data returned by calendar's day_exporter. Don't confuse it with AddonCalendarCalendarDay.
*/
export type AddonCalendarDay = {
seconds: number; // Seconds.
minutes: number; // Minutes.
hours: number; // Hours.
mday: number; // Mday.
wday: number; // Wday.
year: number; // Year.
yday: number; // Yday.
timestamp: number; // Timestamp.
neweventtimestamp: number; // Neweventtimestamp.
viewdaylink?: string; // Viewdaylink.
events: AddonCalendarCalendarEvent[]; // Events.
hasevents: boolean; // Hasevents.
calendareventtypes: string[]; // Calendareventtypes.
previousperiod: number; // Previousperiod.
nextperiod: number; // Nextperiod.
navigation: string; // Navigation.
haslastdayofevent: boolean; // Haslastdayofevent.
};
/**
* Data returned by calendar's day_name_exporter.
*/
export type AddonCalendarDayName = {
dayno: number; // Dayno.
shortname: string; // Shortname.
fullname: string; // Fullname.
};
/**
* Data returned by calendar's calendar_upcoming_exporter.
*/
export type AddonCalendarUpcoming = {
events: AddonCalendarCalendarEvent[]; // Events.
defaulteventcontext: number; // Defaulteventcontext.
filter_selector: string; // Filter_selector.
courseid: number; // Courseid.
categoryid?: number; // Categoryid.
isloggedin: boolean; // Isloggedin.
date: CoreWSDate; // Date.
};
/**
* Result of WS core_calendar_get_calendar_access_information.
*/
export type AddonCalendarGetAccessInfoResult = {
canmanageentries: boolean; // Whether the user can manage entries.
canmanageownentries: boolean; // Whether the user can manage its own entries.
canmanagegroupentries: boolean; // Whether the user can manage group entries.
warnings?: CoreWSExternalWarning[];
};
/**
* Result of WS core_calendar_get_allowed_event_types.
*/
export type AddonCalendarGetAllowedEventTypesResult = {
allowedeventtypes: string[];
warnings?: CoreWSExternalWarning[];
};
/**
* Result of WS core_calendar_get_calendar_events.
*/
export type AddonCalendarGetEventsResult = {
events: AddonCalendarGetEventsEvent[];
warnings?: CoreWSExternalWarning[];
};
/**
* Event data returned by WS core_calendar_get_calendar_events.
*/
export type AddonCalendarGetEventsEvent = {
id: number; // Event id.
name: string; // Event name.
description?: string; // Description.
format: number; // Description format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
courseid: number; // Course id.
categoryid?: number; // @since 3.4. Category id (only for category events).
groupid: number; // Group id.
userid: number; // User id.
repeatid: number; // Repeat id.
modulename?: string; // Module name.
instance: number; // Instance id.
eventtype: string; // Event type.
timestart: number; // Timestart.
timeduration: number; // Time duration.
visible: number; // Visible.
uuid?: string; // Unique id of ical events.
sequence: number; // Sequence.
timemodified: number; // Time modified.
subscriptionid?: number; // Subscription id.
showDate?: boolean; // Calculated in the app. Whether date should be shown before this event.
endsSameDay?: boolean; // Calculated in the app. Whether the event finishes the same day it starts.
deleted?: boolean; // Calculated in the app. Whether it has been deleted in offline.
};
/**
* Result of WS core_calendar_get_calendar_event_by_id.
*/
export type AddonCalendarGetEventByIdResult = {
event: AddonCalendarEvent; // Event.
warnings?: CoreWSExternalWarning[];
};
/**
* Result of WS core_calendar_submit_create_update_form.
*/
export type AddonCalendarSubmitCreateUpdateFormResult = {
event?: AddonCalendarEvent; // Event.
validationerror: boolean; // Invalid form data.
};
/**
* Calculated data for AddonCalendarCalendarEvent.
*/
export type AddonCalendarCalendarEventCalculatedData = {
eventIcon?: string; // Calculated in the app. Event icon.
moduleIcon?: string; // Calculated in the app. Module icon.
formattedType?: string; // Calculated in the app. Formatted type.
duration?: number; // Calculated in the app. Duration of offline event.
format?: number; // Calculated in the app. Format of offline event.
timedurationuntil?: number; // Calculated in the app. Time duration until of offline event.
timedurationminutes?: number; // Calculated in the app. Time duration in minutes of offline event.
deleted?: boolean; // Calculated in the app. Whether it has been deleted in offline.
ispast?: boolean; // Calculated in the app. Whether the event is in the past.
};

View File

@ -16,7 +16,7 @@ import { Injectable } from '@angular/core';
import { CoreLoggerProvider } from '@providers/logger';
import { CoreSitesProvider } from '@providers/sites';
import { CoreCourseProvider } from '@core/course/providers/course';
import { AddonCalendarProvider } from './calendar';
import { AddonCalendarProvider, AddonCalendarCalendarEvent } from './calendar';
import { CoreConstants } from '@core/constants';
import { CoreConfigProvider } from '@providers/config';
import { CoreUtilsProvider } from '@providers/utils/utils';
@ -130,11 +130,11 @@ export class AddonCalendarHelperProvider {
*
* @param e Event to format.
*/
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;
formatEventData(e: AddonCalendarCalendarEvent): void {
e.eventIcon = this.EVENTICONS[e.eventtype] || '';
if (!e.eventIcon) {
e.eventIcon = this.courseProvider.getModuleIconSrc(e.modulename);
e.moduleIcon = e.eventIcon;
}
e.formattedType = this.calendarProvider.getEventType(e);
@ -160,7 +160,7 @@ export class AddonCalendarHelperProvider {
* @param eventTypes Result of getAllowedEventTypes.
* @return Options.
*/
getEventTypeOptions(eventTypes: any): {name: string, value: string}[] {
getEventTypeOptions(eventTypes: {[name: string]: boolean}): {name: string, value: string}[] {
const options = [];
if (eventTypes.user) {

View File

@ -16,7 +16,7 @@ import { Component, ViewChild, Input } from '@angular/core';
import { Content, NavController } from 'ionic-angular';
import { CoreAppProvider } from '@providers/app';
import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { AddonCompetencyProvider } from '../../providers/competency';
import { AddonCompetencyProvider, AddonCompetencyDataForCourseCompetenciesPageResult } from '../../providers/competency';
import { AddonCompetencyHelperProvider } from '../../providers/helper';
/**
@ -33,7 +33,7 @@ export class AddonCompetencyCourseComponent {
@Input() userId: number;
competenciesLoaded = false;
competencies: any;
competencies: AddonCompetencyDataForCourseCompetenciesPageResult;
user: any;
constructor(private navCtrl: NavController, private appProvider: CoreAppProvider, private domUtils: CoreDomUtilsProvider,

View File

@ -17,7 +17,10 @@ import { IonicPage, NavParams } from 'ionic-angular';
import { TranslateService } from '@ngx-translate/core';
import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { CoreSplitViewComponent } from '@components/split-view/split-view';
import { AddonCompetencyProvider } from '../../providers/competency';
import {
AddonCompetencyProvider, AddonCompetencyDataForCourseCompetenciesPageResult, AddonCompetencyDataForPlanPageResult,
AddonCompetencyDataForPlanPageCompetency, AddonCompetencyDataForCourseCompetenciesPageCompetency
} from '../../providers/competency';
/**
* Page that displays the list of competencies of a learning plan.
@ -36,7 +39,7 @@ export class AddonCompetencyCompetenciesPage {
protected userId: number;
competenciesLoaded = false;
competencies = [];
competencies: AddonCompetencyDataForPlanPageCompetency[] | AddonCompetencyDataForCourseCompetenciesPageCompetency[] = [];
title: string;
constructor(navParams: NavParams, private translate: TranslateService, private domUtils: CoreDomUtilsProvider,
@ -59,7 +62,7 @@ export class AddonCompetencyCompetenciesPage {
this.fetchCompetencies().then(() => {
if (!this.competencyId && this.splitviewCtrl.isOn() && this.competencies.length > 0) {
// Take first and load it.
this.openCompetency(this.competencies[0].id);
this.openCompetency(this.competencies[0].competency.id);
}
}).finally(() => {
this.competenciesLoaded = true;
@ -72,7 +75,7 @@ export class AddonCompetencyCompetenciesPage {
* @return Promise resolved when done.
*/
protected fetchCompetencies(): Promise<void> {
let promise;
let promise: Promise<AddonCompetencyDataForPlanPageResult | AddonCompetencyDataForCourseCompetenciesPageResult>;
if (this.planId) {
promise = this.competencyProvider.getLearningPlan(this.planId);
@ -83,13 +86,16 @@ export class AddonCompetencyCompetenciesPage {
}
return promise.then((response) => {
if (response.competencycount <= 0) {
return Promise.reject(this.translate.instant('addon.competency.errornocompetenciesfound'));
}
if (this.planId) {
this.title = response.plan.name;
this.userId = response.plan.userid;
const resp = <AddonCompetencyDataForPlanPageResult> response;
if (resp.competencycount <= 0) {
return Promise.reject(this.translate.instant('addon.competency.errornocompetenciesfound'));
}
this.title = resp.plan.name;
this.userId = resp.plan.userid;
} else {
this.title = this.translate.instant('addon.competency.coursecompetencies');
}

View File

@ -51,22 +51,22 @@
<core-format-text [text]="activity.name"></core-format-text>
</a>
</ion-item>
<ion-item text-wrap *ngIf="competency.usercompetency.status">
<ion-item text-wrap *ngIf="userCompetency.status">
<strong>{{ 'addon.competency.reviewstatus' | translate }}</strong>
{{ competency.usercompetency.statusname }}
{{ userCompetency.statusname }}
</ion-item>
<ion-item text-wrap>
<strong>{{ 'addon.competency.proficient' | translate }}</strong>
<ion-badge color="success" *ngIf="competency.usercompetency.proficiency">
<ion-badge color="success" *ngIf="userCompetency.proficiency">
{{ 'core.yes' | translate }}
</ion-badge>
<ion-badge color="danger" *ngIf="!competency.usercompetency.proficiency">
<ion-badge color="danger" *ngIf="!userCompetency.proficiency">
{{ 'core.no' | translate }}
</ion-badge>
</ion-item>
<ion-item text-wrap>
<strong>{{ 'addon.competency.rating' | translate }}</strong>
<ion-badge color="dark">{{ competency.usercompetency.gradename }}</ion-badge>
<ion-badge color="dark">{{ userCompetency.gradename }}</ion-badge>
</ion-item>
</ion-card>

View File

@ -18,8 +18,14 @@ import { TranslateService } from '@ngx-translate/core';
import { CoreSitesProvider } from '@providers/sites';
import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { CoreSplitViewComponent } from '@components/split-view/split-view';
import { AddonCompetencyProvider } from '../../providers/competency';
import {
AddonCompetencyProvider, AddonCompetencyUserCompetencySummary, AddonCompetencyUserCompetencySummaryInPlan,
AddonCompetencyUserCompetencySummaryInCourse, AddonCompetencyUserCompetencyPlan,
AddonCompetencyUserCompetency, AddonCompetencyUserCompetencyCourse
} from '../../providers/competency';
import { AddonCompetencyHelperProvider } from '../../providers/helper';
import { CoreUserSummary } from '@core/user/providers/user';
import { CoreCourseModuleSummary } from '@core/course/providers/course';
/**
* Page that displays a learning plan.
@ -36,9 +42,10 @@ export class AddonCompetencyCompetencyPage {
courseId: number;
userId: number;
planStatus: number;
coursemodules: any;
user: any;
competency: any;
coursemodules: CoreCourseModuleSummary[];
user: CoreUserSummary;
competency: AddonCompetencyUserCompetencySummary;
userCompetency: AddonCompetencyUserCompetencyPlan | AddonCompetencyUserCompetency | AddonCompetencyUserCompetencyCourse;
constructor(private navCtrl: NavController, navParams: NavParams, private translate: TranslateService,
private sitesProvider: CoreSitesProvider, private domUtils: CoreDomUtilsProvider,
@ -79,7 +86,8 @@ export class AddonCompetencyCompetencyPage {
* @return Promise resolved when done.
*/
protected fetchCompetency(): Promise<void> {
let promise;
let promise: Promise<AddonCompetencyUserCompetencySummaryInPlan | AddonCompetencyUserCompetencySummaryInCourse>;
if (this.planId) {
this.planStatus = null;
promise = this.competencyProvider.getCompetencyInPlan(this.planId, this.competencyId);
@ -90,23 +98,21 @@ export class AddonCompetencyCompetencyPage {
}
return promise.then((competency) => {
competency.usercompetencysummary.usercompetency = competency.usercompetencysummary.usercompetencyplan ||
competency.usercompetencysummary.usercompetency;
this.competency = competency.usercompetencysummary;
this.userCompetency = this.competency.usercompetencyplan || this.competency.usercompetency;
if (this.planId) {
this.planStatus = competency.plan.status;
this.planStatus = (<AddonCompetencyUserCompetencySummaryInPlan> competency).plan.status;
this.competency.usercompetency.statusname =
this.competencyHelperProvider.getCompetencyStatusName(this.competency.usercompetency.status);
} else {
this.competency.usercompetency = this.competency.usercompetencycourse;
this.coursemodules = competency.coursemodules;
this.userCompetency = this.competency.usercompetencycourse;
this.coursemodules = (<AddonCompetencyUserCompetencySummaryInCourse> competency).coursemodules;
}
if (this.competency.user.id != this.sitesProvider.getCurrentSiteUserId()) {
this.competency.user.profileimageurl = this.competency.user.profileimageurl || true;
// Get the user profile image from the returned object.
// Get the user profile from the returned object.
this.user = this.competency.user;
}

View File

@ -16,7 +16,7 @@ import { Component, Optional } from '@angular/core';
import { IonicPage, NavController, NavParams } from 'ionic-angular';
import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { CoreSplitViewComponent } from '@components/split-view/split-view';
import { AddonCompetencyProvider } from '../../providers/competency';
import { AddonCompetencyProvider, AddonCompetencySummary } from '../../providers/competency';
/**
* Page that displays a learning plan.
@ -29,7 +29,7 @@ import { AddonCompetencyProvider } from '../../providers/competency';
export class AddonCompetencyCompetencySummaryPage {
competencyLoaded = false;
competencyId: number;
competency: any;
competency: AddonCompetencySummary;
constructor(private navCtrl: NavController, navParams: NavParams, private domUtils: CoreDomUtilsProvider,
@Optional() private svComponent: CoreSplitViewComponent, private competencyProvider: AddonCompetencyProvider) {
@ -41,8 +41,7 @@ export class AddonCompetencyCompetencySummaryPage {
*/
ionViewDidLoad(): void {
this.fetchCompetency().then(() => {
const name = this.competency.competency && this.competency.competency.competency &&
this.competency.competency.competency.shortname;
const name = this.competency.competency && this.competency.competency.shortname;
this.competencyProvider.logCompetencyView(this.competencyId, name).catch(() => {
// Ignore errors.

View File

@ -46,7 +46,8 @@
</ion-item>
<a ion-item text-wrap *ngFor="let competency of plan.competencies" (click)="openCompetency(competency.competency.id)" [title]="competency.competency.shortname">
<h2>{{competency.competency.shortname}} <em>{{competency.competency.idnumber}}</em></h2>
<ion-badge item-end [color]="competency.usercompetency.proficiency ? 'success' : 'danger'">{{ competency.usercompetency.gradename }}</ion-badge>
<ion-badge *ngIf="competency.usercompetencyplan" item-end [color]="competency.usercompetencyplan.proficiency ? 'success' : 'danger'">{{ competency.usercompetencyplan.gradename }}</ion-badge>
<ion-badge *ngIf="!competency.usercompetencyplan" item-end [color]="competency.usercompetency.proficiency ? 'success' : 'danger'">{{ competency.usercompetency.gradename }}</ion-badge>
</a>
</ion-list>
</ion-card>

View File

@ -17,7 +17,7 @@ import { IonicPage, NavController, NavParams } from 'ionic-angular';
import { CoreAppProvider } from '@providers/app';
import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { CoreSplitViewComponent } from '@components/split-view/split-view';
import { AddonCompetencyProvider } from '../../providers/competency';
import { AddonCompetencyProvider, AddonCompetencyDataForPlanPageResult } from '../../providers/competency';
import { AddonCompetencyHelperProvider } from '../../providers/helper';
/**
@ -31,7 +31,7 @@ import { AddonCompetencyHelperProvider } from '../../providers/helper';
export class AddonCompetencyPlanPage {
protected planId: number;
planLoaded = false;
plan: any;
plan: AddonCompetencyDataForPlanPageResult;
user: any;
constructor(private navCtrl: NavController, navParams: NavParams, private appProvider: CoreAppProvider,
@ -62,9 +62,6 @@ export class AddonCompetencyPlanPage {
this.user = user;
});
plan.competencies.forEach((competency) => {
competency.usercompetency = competency.usercompetencyplan || competency.usercompetency;
});
this.plan = plan;
}).catch((message) => {
this.domUtils.showErrorModalDefault(message, 'Error getting learning plan data.');

View File

@ -16,7 +16,7 @@ import { Component, ViewChild } from '@angular/core';
import { IonicPage, NavParams } from 'ionic-angular';
import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { CoreSplitViewComponent } from '@components/split-view/split-view';
import { AddonCompetencyProvider } from '../../providers/competency';
import { AddonCompetencyProvider, AddonCompetencyPlan } from '../../providers/competency';
import { AddonCompetencyHelperProvider } from '../../providers/helper';
/**
@ -33,7 +33,7 @@ export class AddonCompetencyPlanListPage {
protected userId: number;
protected planId: number;
plansLoaded = false;
plans = [];
plans: AddonCompetencyPlan[] = [];
constructor(navParams: NavParams, private domUtils: CoreDomUtilsProvider, private competencyProvider: AddonCompetencyProvider,
private competencyHelperProvider: AddonCompetencyHelperProvider) {
@ -66,7 +66,7 @@ export class AddonCompetencyPlanListPage {
*/
protected fetchLearningPlans(): Promise<void> {
return this.competencyProvider.getLearningPlans(this.userId).then((plans) => {
plans.forEach((plan) => {
plans.forEach((plan: AddonCompetencyPlanFormatted) => {
plan.statusname = this.competencyHelperProvider.getPlanStatusName(plan.status);
switch (plan.status) {
case AddonCompetencyProvider.STATUS_ACTIVE:
@ -109,3 +109,10 @@ export class AddonCompetencyPlanListPage {
this.splitviewCtrl.push('AddonCompetencyPlanPage', { planId });
}
}
/**
* Competency plan with some calculated data.
*/
type AddonCompetencyPlanFormatted = AddonCompetencyPlan & {
statuscolor?: string; // Calculated in the app. Color of the plan's status.
};

View File

@ -17,6 +17,9 @@ import { CoreLoggerProvider } from '@providers/logger';
import { CoreSitesProvider } from '@providers/sites';
import { CorePushNotificationsProvider } from '@core/pushnotifications/providers/pushnotifications';
import { CoreSite } from '@classes/site';
import { CoreCommentsArea } from '@core/comments/providers/comments';
import { CoreUserSummary } from '@core/user/providers/user';
import { CoreCourseSummary, CoreCourseModuleSummary } from '@core/course/providers/course';
/**
* Service to handle caompetency learning plans.
@ -147,7 +150,7 @@ export class AddonCompetencyProvider {
* @param siteId Site ID. If not defined, current site.
* @return Promise to be resolved when the plans are retrieved.
*/
getLearningPlans(userId?: number, siteId?: string): Promise<any> {
getLearningPlans(userId?: number, siteId?: string): Promise<AddonCompetencyPlan[]> {
return this.sitesProvider.getSite(siteId).then((site) => {
userId = userId || site.getUserId();
@ -161,7 +164,9 @@ export class AddonCompetencyProvider {
updateFrequency: CoreSite.FREQUENCY_RARELY
};
return site.read('tool_lp_data_for_plans_page', params, preSets).then((response) => {
return site.read('tool_lp_data_for_plans_page', params, preSets)
.then((response: AddonCompetencyDataForPlansPageResult): any => {
if (response.plans) {
return response.plans;
}
@ -176,9 +181,9 @@ export class AddonCompetencyProvider {
*
* @param planId ID of the plan.
* @param siteId Site ID. If not defined, current site.
* @return Promise to be resolved when the plans are retrieved.
* @return Promise to be resolved when the plan is retrieved.
*/
getLearningPlan(planId: number, siteId?: string): Promise<any> {
getLearningPlan(planId: number, siteId?: string): Promise<AddonCompetencyDataForPlanPageResult> {
return this.sitesProvider.getSite(siteId).then((site) => {
this.logger.debug('Get plan ' + planId);
@ -191,7 +196,9 @@ export class AddonCompetencyProvider {
updateFrequency: CoreSite.FREQUENCY_RARELY
};
return site.read('tool_lp_data_for_plan_page', params, preSets).then((response) => {
return site.read('tool_lp_data_for_plan_page', params, preSets)
.then((response: AddonCompetencyDataForPlanPageResult): any => {
if (response.plan) {
return response;
}
@ -207,9 +214,11 @@ export class AddonCompetencyProvider {
* @param planId ID of the plan.
* @param competencyId ID of the competency.
* @param siteId Site ID. If not defined, current site.
* @return Promise to be resolved when the plans are retrieved.
* @return Promise to be resolved when the competency is retrieved.
*/
getCompetencyInPlan(planId: number, competencyId: number, siteId?: string): Promise<any> {
getCompetencyInPlan(planId: number, competencyId: number, siteId?: string)
: Promise<AddonCompetencyUserCompetencySummaryInPlan> {
return this.sitesProvider.getSite(siteId).then((site) => {
this.logger.debug('Get competency ' + competencyId + ' in plan ' + planId);
@ -223,7 +232,9 @@ export class AddonCompetencyProvider {
updateFrequency: CoreSite.FREQUENCY_SOMETIMES
};
return site.read('tool_lp_data_for_user_competency_summary_in_plan', params, preSets).then((response) => {
return site.read('tool_lp_data_for_user_competency_summary_in_plan', params, preSets)
.then((response: AddonCompetencyUserCompetencySummaryInPlan): any => {
if (response.usercompetencysummary) {
return response;
}
@ -241,10 +252,10 @@ export class AddonCompetencyProvider {
* @param userId ID of the user. If not defined, current user.
* @param siteId Site ID. If not defined, current site.
* @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down).
* @return Promise to be resolved when the plans are retrieved.
* @return Promise to be resolved when the competency is retrieved.
*/
getCompetencyInCourse(courseId: number, competencyId: number, userId?: number, siteId?: string, ignoreCache?: boolean)
: Promise<any> {
: Promise<AddonCompetencyUserCompetencySummaryInCourse> {
return this.sitesProvider.getSite(siteId).then((site) => {
userId = userId || site.getUserId();
@ -266,7 +277,9 @@ export class AddonCompetencyProvider {
preSets.emergencyCache = false;
}
return site.read('tool_lp_data_for_user_competency_summary_in_course', params, preSets).then((response) => {
return site.read('tool_lp_data_for_user_competency_summary_in_course', params, preSets)
.then((response: AddonCompetencyUserCompetencySummaryInCourse): any => {
if (response.usercompetencysummary) {
return response;
}
@ -283,9 +296,11 @@ export class AddonCompetencyProvider {
* @param userId ID of the user. If not defined, current user.
* @param siteId Site ID. If not defined, current site.
* @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down).
* @return Promise to be resolved when the plans are retrieved.
* @return Promise to be resolved when the competency summary is retrieved.
*/
getCompetencySummary(competencyId: number, userId?: number, siteId?: string, ignoreCache?: boolean): Promise<any> {
getCompetencySummary(competencyId: number, userId?: number, siteId?: string, ignoreCache?: boolean)
: Promise<AddonCompetencySummary> {
return this.sitesProvider.getSite(siteId).then((site) => {
userId = userId || site.getUserId();
@ -305,7 +320,9 @@ export class AddonCompetencyProvider {
preSets.emergencyCache = false;
}
return site.read('tool_lp_data_for_user_competency_summary', params, preSets).then((response) => {
return site.read('tool_lp_data_for_user_competency_summary', params, preSets)
.then((response: AddonCompetencyUserCompetencySummary): any => {
if (response.competency) {
return response.competency;
}
@ -324,7 +341,9 @@ export class AddonCompetencyProvider {
* @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down).
* @return Promise to be resolved when the course competencies are retrieved.
*/
getCourseCompetencies(courseId: number, userId?: number, siteId?: string, ignoreCache?: boolean): Promise<any> {
getCourseCompetencies(courseId: number, userId?: number, siteId?: string, ignoreCache?: boolean)
: Promise<AddonCompetencyDataForCourseCompetenciesPageResult> {
return this.sitesProvider.getSite(siteId).then((site) => {
this.logger.debug('Get course competencies for course ' + courseId);
@ -342,7 +361,9 @@ export class AddonCompetencyProvider {
preSets.emergencyCache = false;
}
return site.read('tool_lp_data_for_course_competencies_page', params, preSets).then((response) => {
return site.read('tool_lp_data_for_course_competencies_page', params, preSets)
.then((response: AddonCompetencyDataForCourseCompetenciesPageResult): any => {
if (response.competencies) {
return response;
}
@ -356,11 +377,13 @@ export class AddonCompetencyProvider {
return response;
}
const promises = response.competencies.map((competency) =>
let promises: Promise<AddonCompetencyUserCompetencySummaryInCourse>[];
promises = response.competencies.map((competency) =>
this.getCompetencyInCourse(courseId, competency.competency.id, userId, siteId)
);
return Promise.all(promises).then((responses: any[]) => {
return Promise.all(promises).then((responses: AddonCompetencyUserCompetencySummaryInCourse[]) => {
responses.forEach((resp, index) => {
response.competencies[index].usercompetencycourse = resp.usercompetencysummary.usercompetencycourse;
});
@ -486,7 +509,7 @@ export class AddonCompetencyProvider {
* @return Promise resolved when the WS call is successful.
*/
logCompetencyInPlanView(planId: number, competencyId: number, planStatus: number, name?: string, userId?: number,
siteId?: string): Promise<any> {
siteId?: string): Promise<void> {
if (planId && competencyId) {
return this.sitesProvider.getSite(siteId).then((site) => {
@ -509,7 +532,11 @@ export class AddonCompetencyProvider {
userid: userId
}, siteId);
return site.write(wsName, params, preSets);
return site.write(wsName, params, preSets).then((success: boolean) => {
if (!success) {
return Promise.reject(null);
}
});
});
}
@ -527,7 +554,7 @@ export class AddonCompetencyProvider {
* @return Promise resolved when the WS call is successful.
*/
logCompetencyInCourseView(courseId: number, competencyId: number, name?: string, userId?: number, siteId?: string)
: Promise<any> {
: Promise<void> {
if (courseId && competencyId) {
return this.sitesProvider.getSite(siteId).then((site) => {
@ -548,7 +575,11 @@ export class AddonCompetencyProvider {
userid: userId
}, siteId);
return site.write(wsName, params, preSets);
return site.write(wsName, params, preSets).then((success: boolean) => {
if (!success) {
return Promise.reject(null);
}
});
});
}
@ -563,7 +594,7 @@ export class AddonCompetencyProvider {
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved when the WS call is successful.
*/
logCompetencyView(competencyId: number, name?: string, siteId?: string): Promise<any> {
logCompetencyView(competencyId: number, name?: string, siteId?: string): Promise<void> {
if (competencyId) {
return this.sitesProvider.getSite(siteId).then((site) => {
const params = {
@ -576,10 +607,401 @@ export class AddonCompetencyProvider {
this.pushNotificationsProvider.logViewEvent(competencyId, name, 'competency', wsName, {}, siteId);
return site.write('core_competency_competency_viewed', params, preSets);
return site.write(wsName, params, preSets).then((success: boolean) => {
if (!success) {
return Promise.reject(null);
}
});
});
}
return Promise.reject(null);
}
}
/**
* Data returned by competency's plan_exporter.
*/
export type AddonCompetencyPlan = {
name: string; // Name.
description: string; // Description.
descriptionformat: number; // Description format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
userid: number; // Userid.
templateid: number; // Templateid.
origtemplateid: number; // Origtemplateid.
status: number; // Status.
duedate: number; // Duedate.
reviewerid: number; // Reviewerid.
id: number; // Id.
timecreated: number; // Timecreated.
timemodified: number; // Timemodified.
usermodified: number; // Usermodified.
statusname: string; // Statusname.
isbasedontemplate: boolean; // Isbasedontemplate.
canmanage: boolean; // Canmanage.
canrequestreview: boolean; // Canrequestreview.
canreview: boolean; // Canreview.
canbeedited: boolean; // Canbeedited.
isactive: boolean; // Isactive.
isdraft: boolean; // Isdraft.
iscompleted: boolean; // Iscompleted.
isinreview: boolean; // Isinreview.
iswaitingforreview: boolean; // Iswaitingforreview.
isreopenallowed: boolean; // Isreopenallowed.
iscompleteallowed: boolean; // Iscompleteallowed.
isunlinkallowed: boolean; // Isunlinkallowed.
isrequestreviewallowed: boolean; // Isrequestreviewallowed.
iscancelreviewrequestallowed: boolean; // Iscancelreviewrequestallowed.
isstartreviewallowed: boolean; // Isstartreviewallowed.
isstopreviewallowed: boolean; // Isstopreviewallowed.
isapproveallowed: boolean; // Isapproveallowed.
isunapproveallowed: boolean; // Isunapproveallowed.
duedateformatted: string; // Duedateformatted.
commentarea: CoreCommentsArea;
reviewer?: CoreUserSummary;
template?: AddonCompetencyTemplate;
url: string; // Url.
};
/**
* Data returned by competency's template_exporter.
*/
export type AddonCompetencyTemplate = {
shortname: string; // Shortname.
description: string; // Description.
descriptionformat: number; // Description format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
duedate: number; // Duedate.
visible: boolean; // Visible.
contextid: number; // Contextid.
id: number; // Id.
timecreated: number; // Timecreated.
timemodified: number; // Timemodified.
usermodified: number; // Usermodified.
duedateformatted: string; // Duedateformatted.
cohortscount: number; // Cohortscount.
planscount: number; // Planscount.
canmanage: boolean; // Canmanage.
canread: boolean; // Canread.
contextname: string; // Contextname.
contextnamenoprefix: string; // Contextnamenoprefix.
};
/**
* Data returned by competency's competency_exporter.
*/
export type AddonCompetencyCompetency = {
shortname: string; // Shortname.
idnumber: string; // Idnumber.
description: string; // Description.
descriptionformat: number; // Description format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
sortorder: number; // Sortorder.
parentid: number; // Parentid.
path: string; // Path.
ruleoutcome: number; // Ruleoutcome.
ruletype: string; // Ruletype.
ruleconfig: string; // Ruleconfig.
scaleid: number; // Scaleid.
scaleconfiguration: string; // Scaleconfiguration.
competencyframeworkid: number; // Competencyframeworkid.
id: number; // Id.
timecreated: number; // Timecreated.
timemodified: number; // Timemodified.
usermodified: number; // Usermodified.
};
/**
* Data returned by competency's competency_path_exporter.
*/
export type AddonCompetencyPath = {
ancestors: AddonCompetencyPathNode[]; // Ancestors.
framework: AddonCompetencyPathNode;
pluginbaseurl: string; // Pluginbaseurl.
pagecontextid: number; // Pagecontextid.
showlinks: boolean; // Showlinks.
};
/**
* Data returned by competency's path_node_exporter.
*/
export type AddonCompetencyPathNode = {
id: number; // Id.
name: string; // Name.
first: boolean; // First.
last: boolean; // Last.
position: number; // Position.
};
/**
* Data returned by competency's user_competency_exporter.
*/
export type AddonCompetencyUserCompetency = {
userid: number; // Userid.
competencyid: number; // Competencyid.
status: number; // Status.
reviewerid: number; // Reviewerid.
proficiency: boolean; // Proficiency.
grade: number; // Grade.
id: number; // Id.
timecreated: number; // Timecreated.
timemodified: number; // Timemodified.
usermodified: number; // Usermodified.
canrequestreview: boolean; // Canrequestreview.
canreview: boolean; // Canreview.
gradename: string; // Gradename.
isrequestreviewallowed: boolean; // Isrequestreviewallowed.
iscancelreviewrequestallowed: boolean; // Iscancelreviewrequestallowed.
isstartreviewallowed: boolean; // Isstartreviewallowed.
isstopreviewallowed: boolean; // Isstopreviewallowed.
isstatusidle: boolean; // Isstatusidle.
isstatusinreview: boolean; // Isstatusinreview.
isstatuswaitingforreview: boolean; // Isstatuswaitingforreview.
proficiencyname: string; // Proficiencyname.
reviewer?: CoreUserSummary;
statusname: string; // Statusname.
url: string; // Url.
};
/**
* Data returned by competency's user_competency_plan_exporter.
*/
export type AddonCompetencyUserCompetencyPlan = {
userid: number; // Userid.
competencyid: number; // Competencyid.
proficiency: boolean; // Proficiency.
grade: number; // Grade.
planid: number; // Planid.
sortorder: number; // Sortorder.
id: number; // Id.
timecreated: number; // Timecreated.
timemodified: number; // Timemodified.
usermodified: number; // Usermodified.
gradename: string; // Gradename.
proficiencyname: string; // Proficiencyname.
};
/**
* Data returned by competency's user_competency_summary_in_plan_exporter.
*/
export type AddonCompetencyUserCompetencySummaryInPlan = {
usercompetencysummary: AddonCompetencyUserCompetencySummary;
plan: AddonCompetencyPlan;
};
/**
* Data returned by competency's user_competency_summary_exporter.
*/
export type AddonCompetencyUserCompetencySummary = {
showrelatedcompetencies: boolean; // Showrelatedcompetencies.
cangrade: boolean; // Cangrade.
competency: AddonCompetencySummary;
user: CoreUserSummary;
usercompetency?: AddonCompetencyUserCompetency;
usercompetencyplan?: AddonCompetencyUserCompetencyPlan;
usercompetencycourse?: AddonCompetencyUserCompetencyCourse;
evidence: AddonCompetencyEvidence[]; // Evidence.
commentarea?: CoreCommentsArea;
};
/**
* Data returned by competency's competency_summary_exporter.
*/
export type AddonCompetencySummary = {
linkedcourses: CoreCourseSummary; // Linkedcourses.
relatedcompetencies: AddonCompetencyCompetency[]; // Relatedcompetencies.
competency: AddonCompetencyCompetency;
framework: AddonCompetencyFramework;
hascourses: boolean; // Hascourses.
hasrelatedcompetencies: boolean; // Hasrelatedcompetencies.
scaleid: number; // Scaleid.
scaleconfiguration: string; // Scaleconfiguration.
taxonomyterm: string; // Taxonomyterm.
comppath: AddonCompetencyPath;
pluginbaseurl: string; // Pluginbaseurl.
};
/**
* Data returned by competency's competency_framework_exporter.
*/
export type AddonCompetencyFramework = {
shortname: string; // Shortname.
idnumber: string; // Idnumber.
description: string; // Description.
descriptionformat: number; // Description format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
visible: boolean; // Visible.
scaleid: number; // Scaleid.
scaleconfiguration: string; // Scaleconfiguration.
contextid: number; // Contextid.
taxonomies: string; // Taxonomies.
id: number; // Id.
timecreated: number; // Timecreated.
timemodified: number; // Timemodified.
usermodified: number; // Usermodified.
canmanage: boolean; // Canmanage.
competenciescount: number; // Competenciescount.
contextname: string; // Contextname.
contextnamenoprefix: string; // Contextnamenoprefix.
};
/**
* Data returned by competency's user_competency_course_exporter.
*/
export type AddonCompetencyUserCompetencyCourse = {
userid: number; // Userid.
courseid: number; // Courseid.
competencyid: number; // Competencyid.
proficiency: boolean; // Proficiency.
grade: number; // Grade.
id: number; // Id.
timecreated: number; // Timecreated.
timemodified: number; // Timemodified.
usermodified: number; // Usermodified.
gradename: string; // Gradename.
proficiencyname: string; // Proficiencyname.
};
/**
* Data returned by competency's evidence_exporter.
*/
export type AddonCompetencyEvidence = {
usercompetencyid: number; // Usercompetencyid.
contextid: number; // Contextid.
action: number; // Action.
actionuserid: number; // Actionuserid.
descidentifier: string; // Descidentifier.
desccomponent: string; // Desccomponent.
desca: string; // Desca.
url: string; // Url.
grade: number; // Grade.
note: string; // Note.
id: number; // Id.
timecreated: number; // Timecreated.
timemodified: number; // Timemodified.
usermodified: number; // Usermodified.
actionuser?: CoreUserSummary;
description: string; // Description.
gradename: string; // Gradename.
userdate: string; // Userdate.
candelete: boolean; // Candelete.
};
/**
* Data returned by competency's user_competency_summary_in_course_exporter.
*/
export type AddonCompetencyUserCompetencySummaryInCourse = {
usercompetencysummary: AddonCompetencyUserCompetencySummary;
course: CoreCourseSummary;
coursemodules: CoreCourseModuleSummary[]; // Coursemodules.
plans: AddonCompetencyPlan[]; // Plans.
pluginbaseurl: string; // Pluginbaseurl.
};
/**
* Data returned by competency's course_competency_settings_exporter.
*/
export type AddonCompetencyCourseCompetencySettings = {
courseid: number; // Courseid.
pushratingstouserplans: boolean; // Pushratingstouserplans.
id: number; // Id.
timecreated: number; // Timecreated.
timemodified: number; // Timemodified.
usermodified: number; // Usermodified.
};
/**
* Data returned by competency's course_competency_statistics_exporter.
*/
export type AddonCompetencyCourseCompetencyStatistics = {
competencycount: number; // Competencycount.
proficientcompetencycount: number; // Proficientcompetencycount.
proficientcompetencypercentage: number; // Proficientcompetencypercentage.
proficientcompetencypercentageformatted: string; // Proficientcompetencypercentageformatted.
leastproficient: AddonCompetencyCompetency[]; // Leastproficient.
leastproficientcount: number; // Leastproficientcount.
canbegradedincourse: boolean; // Canbegradedincourse.
canmanagecoursecompetencies: boolean; // Canmanagecoursecompetencies.
};
/**
* Data returned by competency's course_competency_exporter.
*/
export type AddonCompetencyCourseCompetency = {
courseid: number; // Courseid.
competencyid: number; // Competencyid.
sortorder: number; // Sortorder.
ruleoutcome: number; // Ruleoutcome.
id: number; // Id.
timecreated: number; // Timecreated.
timemodified: number; // Timemodified.
usermodified: number; // Usermodified.
};
/**
* Result of WS tool_lp_data_for_plans_page.
*/
export type AddonCompetencyDataForPlansPageResult = {
userid: number; // The learning plan user id.
plans: AddonCompetencyPlan[];
pluginbaseurl: string; // Url to the tool_lp plugin folder on this Moodle site.
navigation: string[];
canreaduserevidence: boolean; // Can the current user view the user's evidence.
canmanageuserplans: boolean; // Can the current user manage the user's plans.
};
/**
* Result of WS tool_lp_data_for_plan_page.
*/
export type AddonCompetencyDataForPlanPageResult = {
plan: AddonCompetencyPlan;
contextid: number; // Context ID.
pluginbaseurl: string; // Plugin base URL.
competencies: AddonCompetencyDataForPlanPageCompetency[];
competencycount: number; // Count of competencies.
proficientcompetencycount: number; // Count of proficientcompetencies.
proficientcompetencypercentage: number; // Percentage of competencies proficient.
proficientcompetencypercentageformatted: string; // Displayable percentage.
};
/**
* Competency data returned by tool_lp_data_for_plan_page.
*/
export type AddonCompetencyDataForPlanPageCompetency = {
competency: AddonCompetencyCompetency;
comppath: AddonCompetencyPath;
usercompetency?: AddonCompetencyUserCompetency;
usercompetencyplan?: AddonCompetencyUserCompetencyPlan;
};
/**
* Result of WS tool_lp_data_for_course_competencies_page.
*/
export type AddonCompetencyDataForCourseCompetenciesPageResult = {
courseid: number; // The current course id.
pagecontextid: number; // The current page context ID.
gradableuserid?: number; // Current user id, if the user is a gradable user.
canmanagecompetencyframeworks: boolean; // User can manage competency frameworks.
canmanagecoursecompetencies: boolean; // User can manage linked course competencies.
canconfigurecoursecompetencies: boolean; // User can configure course competency settings.
cangradecompetencies: boolean; // User can grade competencies.
settings: AddonCompetencyCourseCompetencySettings;
statistics: AddonCompetencyCourseCompetencyStatistics;
competencies: AddonCompetencyDataForCourseCompetenciesPageCompetency[];
manageurl: string; // Url to the manage competencies page.
pluginbaseurl: string; // Url to the course competencies page.
};
/**
* Competency data returned by tool_lp_data_for_course_competencies_page.
*/
export type AddonCompetencyDataForCourseCompetenciesPageCompetency = {
competency: AddonCompetencyCompetency;
coursecompetency: AddonCompetencyCourseCompetency;
coursemodules: CoreCourseModuleSummary[];
usercompetencycourse?: AddonCompetencyUserCompetencyCourse;
ruleoutcomeoptions: {
value: number; // The option value.
text: string; // The name of the option.
selected: boolean; // If this is the currently selected option.
}[];
comppath: AddonCompetencyPath;
plans: AddonCompetencyPlan[];
};

View File

@ -6,7 +6,7 @@
<ion-card *ngIf="completion && tracked">
<ion-item text-wrap>
<h2>{{ 'addon.coursecompletion.status' | translate }}</h2>
<p>{{ completion.statusText | translate }}</p>
<p>{{ statusText | translate }}</p>
</ion-item>
<ion-item text-wrap>
<h2>{{ 'addon.coursecompletion.required' | translate }}</h2>

View File

@ -15,7 +15,7 @@
import { Component, Input, OnInit } from '@angular/core';
import { CoreSitesProvider } from '@providers/sites';
import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { AddonCourseCompletionProvider } from '../../providers/coursecompletion';
import { AddonCourseCompletionProvider, AddonCourseCompletionCourseCompletionStatus } from '../../providers/coursecompletion';
/**
* Component that displays the course completion report.
@ -29,9 +29,10 @@ export class AddonCourseCompletionReportComponent implements OnInit {
@Input() userId: number;
completionLoaded = false;
completion: any;
completion: AddonCourseCompletionCourseCompletionStatus;
showSelfComplete: boolean;
tracked = true; // Whether completion is tracked.
statusText: string;
constructor(
private sitesProvider: CoreSitesProvider,
@ -59,7 +60,7 @@ export class AddonCourseCompletionReportComponent implements OnInit {
protected fetchCompletion(): Promise<any> {
return this.courseCompletionProvider.getCompletion(this.courseId, this.userId).then((completion) => {
completion.statusText = this.courseCompletionProvider.getCompletedStatusText(completion);
this.statusText = this.courseCompletionProvider.getCompletedStatusText(completion);
this.completion = completion;
this.showSelfComplete = this.courseCompletionProvider.canMarkSelfCompleted(this.userId, completion);

View File

@ -18,6 +18,7 @@ import { CoreSitesProvider } from '@providers/sites';
import { CoreUtilsProvider } from '@providers/utils/utils';
import { CoreCoursesProvider } from '@core/courses/providers/courses';
import { CoreSite } from '@classes/site';
import { CoreWSExternalWarning } from '@providers/ws';
/**
* Service to handle course completion.
@ -43,7 +44,7 @@ export class AddonCourseCompletionProvider {
* @param completion Course completion.
* @return True if user can mark course as self completed, false otherwise.
*/
canMarkSelfCompleted(userId: number, completion: any): boolean {
canMarkSelfCompleted(userId: number, completion: AddonCourseCompletionCourseCompletionStatus): boolean {
let selfCompletionActive = false,
alreadyMarked = false;
@ -68,7 +69,7 @@ export class AddonCourseCompletionProvider {
* @param completion Course completion.
* @return Language code of the text to show.
*/
getCompletedStatusText(completion: any): string {
getCompletedStatusText(completion: AddonCourseCompletionCourseCompletionStatus): string {
if (completion.completed) {
return 'addon.coursecompletion.completed';
} else {
@ -96,7 +97,9 @@ export class AddonCourseCompletionProvider {
* @param siteId Site ID. If not defined, use current site.
* @return Promise to be resolved when the completion is retrieved.
*/
getCompletion(courseId: number, userId?: number, preSets?: any, siteId?: string): Promise<any> {
getCompletion(courseId: number, userId?: number, preSets?: any, siteId?: string)
: Promise<AddonCourseCompletionCourseCompletionStatus> {
return this.sitesProvider.getSite(siteId).then((site) => {
userId = userId || site.getUserId();
preSets = preSets || {};
@ -112,7 +115,9 @@ export class AddonCourseCompletionProvider {
preSets.updateFrequency = preSets.updateFrequency || CoreSite.FREQUENCY_SOMETIMES;
preSets.cacheErrors = ['notenroled'];
return site.read('core_completion_get_course_completion_status', data, preSets).then((data) => {
return site.read('core_completion_get_course_completion_status', data, preSets)
.then((data: AddonCourseCompletionGetCourseCompletionStatusResult): any => {
if (data.completionstatus) {
return data.completionstatus;
}
@ -243,17 +248,56 @@ export class AddonCourseCompletionProvider {
* Mark a course as self completed.
*
* @param courseId Course ID.
* @return Resolved on success.
* @return Promise resolved on success.
*/
markCourseAsSelfCompleted(courseId: number): Promise<any> {
markCourseAsSelfCompleted(courseId: number): Promise<void> {
const params = {
courseid: courseId
};
return this.sitesProvider.getCurrentSite().write('core_completion_mark_course_self_completed', params).then((response) => {
return this.sitesProvider.getCurrentSite().write('core_completion_mark_course_self_completed', params)
.then((response: AddonCourseCompletionMarkCourseSelfCompletedResult) => {
if (!response.status) {
return Promise.reject(null);
}
});
}
}
/**
* Completion status returned by core_completion_get_course_completion_status.
*/
export type AddonCourseCompletionCourseCompletionStatus = {
completed: boolean; // True if the course is complete, false otherwise.
aggregation: number; // Aggregation method 1 means all, 2 means any.
completions: {
type: number; // Completion criteria type.
title: string; // Completion criteria Title.
status: string; // Completion status (Yes/No) a % or number.
complete: boolean; // Completion status (true/false).
timecompleted: number; // Timestamp for criteria completetion.
details: {
type: string; // Type description.
criteria: string; // Criteria description.
requirement: string; // Requirement description.
status: string; // Status description, can be anything.
}; // Details.
}[];
};
/**
* Result of WS core_completion_get_course_completion_status.
*/
export type AddonCourseCompletionGetCourseCompletionStatusResult = {
completionstatus: AddonCourseCompletionCourseCompletionStatus; // Course status.
warnings?: CoreWSExternalWarning[];
};
/**
* Result of WS core_completion_mark_course_self_completed.
*/
export type AddonCourseCompletionMarkCourseSelfCompletedResult = {
status: boolean; // Status, true if success.
warnings?: CoreWSExternalWarning[];
};

View File

@ -20,7 +20,7 @@ import { CoreEventsProvider } from '@providers/events';
import { CoreSitesProvider } from '@providers/sites';
import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { CoreTextUtilsProvider } from '@providers/utils/text';
import { AddonFilesProvider } from '../../providers/files';
import { AddonFilesProvider, AddonFilesFile, AddonFilesGetUserPrivateFilesInfoResult } from '../../providers/files';
import { AddonFilesHelperProvider } from '../../providers/helper';
/**
@ -40,10 +40,10 @@ export class AddonFilesListPage implements OnDestroy {
root: string; // The root of the files loaded: 'my' or 'site'.
path: string; // The path of the directory being loaded. If empty path it means the root is being loaded.
userQuota: number; // The user quota (in bytes).
filesInfo: any; // Info about private files (size, number of files, etc.).
filesInfo: AddonFilesGetUserPrivateFilesInfoResult; // Info about private files (size, number of files, etc.).
spaceUsed: string; // Space used in a readable format.
userQuotaReadable: string; // User quota in a readable format.
files: any[]; // List of files.
files: AddonFilesFile[]; // List of files.
component: string; // Component to link the file downloads to.
filesLoaded: boolean; // Whether the files are loaded.
@ -147,7 +147,7 @@ export class AddonFilesListPage implements OnDestroy {
* @return Promise resolved when done.
*/
protected fetchFiles(): Promise<any> {
let promise;
let promise: Promise<AddonFilesFile[]>;
if (!this.path) {
// The path is unknown, the user must be requesting a root.

View File

@ -16,6 +16,7 @@ import { Injectable } from '@angular/core';
import { CoreSitesProvider } from '@providers/sites';
import { CoreMimetypeUtilsProvider } from '@providers/utils/mimetype';
import { CoreSite } from '@classes/site';
import { CoreWSExternalWarning } from '@providers/ws';
/**
* Service to handle my files and site files.
@ -73,7 +74,7 @@ export class AddonFilesProvider {
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved with the files.
*/
getFiles(params: any, siteId?: string): Promise<any[]> {
getFiles(params: any, siteId?: string): Promise<AddonFilesFile[]> {
return this.sitesProvider.getSite(siteId).then((site) => {
const preSets = {
@ -82,15 +83,15 @@ export class AddonFilesProvider {
};
return site.read('core_files_get_files', params, preSets);
}).then((result) => {
const entries = [];
}).then((result: AddonFilesGetFilesResult) => {
const entries: AddonFilesFile[] = [];
if (result.files) {
result.files.forEach((entry) => {
if (entry.isdir) {
// Create a "link" to load the folder.
entry.link = {
contextid: entry.contextid || '',
contextid: entry.contextid || null,
component: entry.component || '',
filearea: entry.filearea || '',
itemid: entry.itemid || 0,
@ -135,7 +136,7 @@ export class AddonFilesProvider {
*
* @return Promise resolved with the files.
*/
getPrivateFiles(): Promise<any[]> {
getPrivateFiles(): Promise<AddonFilesFile[]> {
return this.getFiles(this.getPrivateFilesRootParams());
}
@ -164,7 +165,7 @@ export class AddonFilesProvider {
* @param siteId Site ID. If not defined, use current site.
* @return Promise resolved with the info.
*/
getPrivateFilesInfo(userId?: number, siteId?: string): Promise<any> {
getPrivateFilesInfo(userId?: number, siteId?: string): Promise<AddonFilesGetUserPrivateFilesInfoResult> {
return this.sitesProvider.getSite(siteId).then((site) => {
userId = userId || site.getUserId();
@ -204,7 +205,7 @@ export class AddonFilesProvider {
*
* @return Promise resolved with the files.
*/
getSiteFiles(): Promise<any[]> {
getSiteFiles(): Promise<AddonFilesFile[]> {
return this.getFiles(this.getSiteFilesRootParams());
}
@ -388,7 +389,7 @@ export class AddonFilesProvider {
* @param siteid ID of the site. If not defined, use current site.
* @return Promise resolved in success, rejected otherwise.
*/
moveFromDraftToPrivate(draftId: number, siteId?: string): Promise<any> {
moveFromDraftToPrivate(draftId: number, siteId?: string): Promise<null> {
const params = {
draftid: draftId
},
@ -414,3 +415,63 @@ export class AddonFilesProvider {
});
}
}
/**
* File data returned by core_files_get_files.
*/
export type AddonFilesFile = {
contextid: number;
component: string;
filearea: string;
itemid: number;
filepath: string;
filename: string;
isdir: boolean;
url: string;
timemodified: number;
timecreated?: number; // Time created.
filesize?: number; // File size.
author?: string; // File owner.
license?: string; // File license.
} & AddonFilesFileCalculatedData;
/**
* Result of WS core_files_get_files.
*/
export type AddonFilesGetFilesResult = {
parents: {
contextid: number;
component: string;
filearea: string;
itemid: number;
filepath: string;
filename: string;
}[];
files: AddonFilesFile[];
};
/**
* Result of WS core_user_get_private_files_info.
*/
export type AddonFilesGetUserPrivateFilesInfoResult = {
filecount: number; // Number of files in the area.
foldercount: number; // Number of folders in the area.
filesize: number; // Total size of the files in the area.
filesizewithoutreferences: number; // Total size of the area excluding file references.
warnings?: CoreWSExternalWarning[];
};
/**
* Calculated data for AddonFilesFile.
*/
export type AddonFilesFileCalculatedData = {
link?: { // Calculated in the app. A link to open the folder.
contextid?: number; // Folder's contextid.
component?: string; // Folder's component.
filearea?: string; // Folder's filearea.
itemid?: number; // Folder's itemid.
filepath?: string; // Folder's filepath.
filename?: string; // Folder's filename.
};
imgPath?: string; // Path to file icon's image.
};

View File

@ -16,7 +16,7 @@ import { Component, OnDestroy } from '@angular/core';
import { IonicPage } from 'ionic-angular';
import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { CorePushNotificationsProvider } from '@core/pushnotifications/providers/pushnotifications';
import { AddonMessageOutputAirnotifierProvider } from '../../providers/airnotifier';
import { AddonMessageOutputAirnotifierProvider, AddonMessageOutputAirnotifierDevice } from '../../providers/airnotifier';
/**
* Page that displays the list of devices.
@ -28,7 +28,7 @@ import { AddonMessageOutputAirnotifierProvider } from '../../providers/airnotifi
})
export class AddonMessageOutputAirnotifierDevicesPage implements OnDestroy {
devices = [];
devices: AddonMessageOutputAirnotifierDeviceFormatted[] = [];
devicesLoaded = false;
protected updateTimeout: any;
@ -54,7 +54,7 @@ export class AddonMessageOutputAirnotifierDevicesPage implements OnDestroy {
const pushId = this.pushNotificationsProvider.getPushId();
// Convert enabled to boolean and search current device.
devices.forEach((device) => {
devices.forEach((device: AddonMessageOutputAirnotifierDeviceFormatted) => {
device.enable = !!device.enable;
device.current = pushId && pushId == device.pushid;
});
@ -110,8 +110,9 @@ export class AddonMessageOutputAirnotifierDevicesPage implements OnDestroy {
* @param device The device object.
* @param enable True to enable the device, false to disable it.
*/
enableDevice(device: any, enable: boolean): void {
enableDevice(device: AddonMessageOutputAirnotifierDeviceFormatted, enable: boolean): void {
device.updating = true;
this.airnotifierProivder.enableDevice(device.id, enable).then(() => {
// Update the list of devices since it was modified.
this.updateDevicesAfterDelay();
@ -135,3 +136,11 @@ export class AddonMessageOutputAirnotifierDevicesPage implements OnDestroy {
}
}
}
/**
* User device with some calculated data.
*/
type AddonMessageOutputAirnotifierDeviceFormatted = AddonMessageOutputAirnotifierDevice & {
current?: boolean; // Calculated in the app. Whether it's the current device.
updating?: boolean; // Calculated in the app. Whether the device enable is being updated right now.
};

View File

@ -17,6 +17,7 @@ import { CoreLoggerProvider } from '@providers/logger';
import { CoreSitesProvider } from '@providers/sites';
import { CoreConfigConstants } from '../../../../configconstants';
import { CoreSite } from '@classes/site';
import { CoreWSExternalWarning } from '@providers/ws';
/**
* Service to handle Airnotifier message output.
@ -39,14 +40,16 @@ export class AddonMessageOutputAirnotifierProvider {
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved if success.
*/
enableDevice(deviceId: number, enable: boolean, siteId?: string): Promise<any> {
enableDevice(deviceId: number, enable: boolean, siteId?: string): Promise<void> {
return this.sitesProvider.getSite(siteId).then((site) => {
const data = {
deviceid: deviceId,
enable: enable ? 1 : 0
};
return site.write('message_airnotifier_enable_device', data).then((result) => {
return site.write('message_airnotifier_enable_device', data)
.then((result: AddonMessageOutputAirnotifierEnableDeviceResult) => {
if (!result.success) {
// Fail. Reject with warning message if any.
if (result.warnings && result.warnings.length) {
@ -74,7 +77,7 @@ export class AddonMessageOutputAirnotifierProvider {
* @param siteId Site ID. If not defined, use current site.
* @return Promise resolved with the devices.
*/
getUserDevices(siteId?: string): Promise<any> {
getUserDevices(siteId?: string): Promise<AddonMessageOutputAirnotifierDevice[]> {
this.logger.debug('Get user devices');
return this.sitesProvider.getSite(siteId).then((site) => {
@ -86,7 +89,8 @@ export class AddonMessageOutputAirnotifierProvider {
updateFrequency: CoreSite.FREQUENCY_RARELY
};
return site.read('message_airnotifier_get_user_devices', data, preSets).then((data) => {
return site.read('message_airnotifier_get_user_devices', data, preSets)
.then((data: AddonMessageOutputAirnotifierGetUserDevicesResult) => {
return data.devices;
});
});
@ -115,3 +119,36 @@ export class AddonMessageOutputAirnotifierProvider {
this.sitesProvider.wsAvailableInCurrentSite('message_airnotifier_get_user_devices');
}
}
/**
* Device data returned by WS message_airnotifier_get_user_devices.
*/
export type AddonMessageOutputAirnotifierDevice = {
id: number; // Device id (in the message_airnotifier table).
appid: string; // The app id, something like com.moodle.moodlemobile.
name: string; // The device name, 'occam' or 'iPhone' etc.
model: string; // The device model 'Nexus4' or 'iPad1,1' etc.
platform: string; // The device platform 'iOS' or 'Android' etc.
version: string; // The device version '6.1.2' or '4.2.2' etc.
pushid: string; // The device PUSH token/key/identifier/registration id.
uuid: string; // The device UUID.
enable: number | boolean; // Whether the device is enabled or not.
timecreated: number; // Time created.
timemodified: number; // Time modified.
};
/**
* Result of WS message_airnotifier_enable_device.
*/
export type AddonMessageOutputAirnotifierEnableDeviceResult = {
success: boolean; // True if success.
warnings?: CoreWSExternalWarning[];
};
/**
* Result of WS message_airnotifier_get_user_devices.
*/
export type AddonMessageOutputAirnotifierGetUserDevicesResult = {
devices: AddonMessageOutputAirnotifierDevice[]; // List of devices.
warnings?: CoreWSExternalWarning[];
};

View File

@ -16,7 +16,7 @@ import { Component, EventEmitter, OnDestroy, OnInit, Output, ViewChild } from '@
import { Content } from 'ionic-angular';
import { CoreEventsProvider } from '@providers/events';
import { CoreSitesProvider } from '@providers/sites';
import { AddonMessagesProvider } from '../../providers/messages';
import { AddonMessagesProvider, AddonMessagesConversationMember } from '../../providers/messages';
import { CoreDomUtilsProvider } from '@providers/utils/dom';
/**
@ -33,7 +33,7 @@ export class AddonMessagesConfirmedContactsComponent implements OnInit, OnDestro
loaded = false;
canLoadMore = false;
loadMoreError = false;
contacts = [];
contacts: AddonMessagesConversationMember[] = [];
selectedUserId: number;
protected memberInfoObserver;

View File

@ -16,7 +16,7 @@ import { Component, EventEmitter, OnDestroy, OnInit, Output, ViewChild } from '@
import { Content } from 'ionic-angular';
import { CoreEventsProvider } from '@providers/events';
import { CoreSitesProvider } from '@providers/sites';
import { AddonMessagesProvider } from '../../providers/messages';
import { AddonMessagesProvider, AddonMessagesConversationMember } from '../../providers/messages';
import { CoreDomUtilsProvider } from '@providers/utils/dom';
/**
@ -33,7 +33,7 @@ export class AddonMessagesContactRequestsComponent implements OnInit, OnDestroy
loaded = false;
canLoadMore = false;
loadMoreError = false;
requests = [];
requests: AddonMessagesConversationMember[] = [];
selectedUserId: number;
protected memberInfoObserver;

View File

@ -16,7 +16,9 @@ import { Component } from '@angular/core';
import { NavParams } from 'ionic-angular';
import { TranslateService } from '@ngx-translate/core';
import { CoreSitesProvider } from '@providers/sites';
import { AddonMessagesProvider } from '../../providers/messages';
import {
AddonMessagesProvider, AddonMessagesGetContactsResult, AddonMessagesSearchContactsContact
} from '../../providers/messages';
import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { CoreAppProvider } from '@providers/app';
import { CoreEventsProvider } from '@providers/events';
@ -42,7 +44,10 @@ export class AddonMessagesContactsComponent {
searchType = 'search';
loadingMessage = '';
hasContacts = false;
contacts = {
contacts: AddonMessagesGetContactsFormatted = {
online: [],
offline: [],
strangers: [],
search: []
};
searchString = '';
@ -205,7 +210,7 @@ export class AddonMessagesContactsComponent {
this.searchString = query;
this.contactTypes = ['search'];
this.contacts['search'] = this.sortUsers(result);
this.contacts.search = this.sortUsers(result);
}).catch((error) => {
this.domUtils.showErrorModalDefault(error, 'addon.messages.errorwhileretrievingcontacts', true);
});
@ -234,3 +239,10 @@ export class AddonMessagesContactsComponent {
this.memberInfoObserver && this.memberInfoObserver.off();
}
}
/**
* Contacts with some calculated data.
*/
export type AddonMessagesGetContactsFormatted = AddonMessagesGetContactsResult & {
search?: AddonMessagesSearchContactsContact[]; // Calculated in the app. Result of searching users.
};

View File

@ -14,7 +14,9 @@
import { Component, OnInit } from '@angular/core';
import { IonicPage, NavParams, ViewController } from 'ionic-angular';
import { AddonMessagesProvider } from '../../providers/messages';
import {
AddonMessagesProvider, AddonMessagesConversationFormatted, AddonMessagesConversationMember
} from '../../providers/messages';
import { CoreDomUtilsProvider } from '@providers/utils/dom';
/**
@ -28,8 +30,8 @@ import { CoreDomUtilsProvider } from '@providers/utils/dom';
export class AddonMessagesConversationInfoPage implements OnInit {
loaded = false;
conversation: any;
members = [];
conversation: AddonMessagesConversationFormatted;
members: AddonMessagesConversationMember[] = [];
canLoadMore = false;
loadMoreError = false;

View File

@ -17,7 +17,10 @@ import { IonicPage, NavParams, NavController, Content, ModalController } from 'i
import { TranslateService } from '@ngx-translate/core';
import { CoreEventsProvider } from '@providers/events';
import { CoreSitesProvider } from '@providers/sites';
import { AddonMessagesProvider } from '../../providers/messages';
import {
AddonMessagesProvider, AddonMessagesConversationFormatted, AddonMessagesConversationMember, AddonMessagesConversationMessage,
AddonMessagesGetMessagesMessage
} from '../../providers/messages';
import { AddonMessagesOfflineProvider } from '../../providers/messages-offline';
import { AddonMessagesSyncProvider } from '../../providers/sync';
import { CoreUserProvider } from '@core/user/providers/user';
@ -54,7 +57,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
protected messagesBeingSent = 0;
protected pagesLoaded = 1;
protected lastMessage = {text: '', timecreated: 0};
protected keepMessageMap = {};
protected keepMessageMap: {[hash: string]: boolean} = {};
protected syncObserver: any;
protected oldContentHeight = 0;
protected keyboardObserver: any;
@ -64,7 +67,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
protected showLoadingModal = false; // Whether to show a loading modal while fetching data.
conversationId: number; // Conversation ID. Undefined if it's a new individual conversation.
conversation: any; // The conversation object (if it exists).
conversation: AddonMessagesConversationFormatted; // The conversation object (if it exists).
userId: number; // User ID you're talking to (only if group messaging not enabled or it's a new individual conversation).
currentUserId: number;
title: string;
@ -74,18 +77,18 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
showKeyboard = false;
canLoadMore = false;
loadMoreError = false;
messages = [];
messages: (AddonMessagesConversationMessageFormatted | AddonMessagesGetMessagesMessageFormatted)[] = [];
showDelete = false;
canDelete = false;
groupMessagingEnabled: boolean;
isGroup = false;
members: any = {}; // Members that wrote a message, indexed by ID.
members: {[id: number]: AddonMessagesConversationMember} = {}; // Members that wrote a message, indexed by ID.
favouriteIcon = 'fa-star';
favouriteIconSlash = false;
deleteIcon = 'trash';
blockIcon = 'close-circle';
addRemoveIcon = 'person';
otherMember: any; // Other member information (individual conversations only).
otherMember: AddonMessagesConversationMember; // Other member information (individual conversations only).
footerType: 'message' | 'blocked' | 'requiresContact' | 'requestSent' | 'requestReceived' | 'unable';
requestContactSent = false;
requestContactReceived = false;
@ -139,7 +142,9 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
* @param message Message to be added.
* @param keep If set the keep flag or not.
*/
protected addMessage(message: any, keep: boolean = true): void {
protected addMessage(message: AddonMessagesConversationMessageFormatted | AddonMessagesGetMessagesMessageFormatted,
keep: boolean = true): void {
/* Create a hash to identify the message. The text of online messages isn't reliable because it can have random data
like VideoJS ID. Try to use id and fallback to text for offline messages. */
message.hash = Md5.hashAsciiStr(String(message.id || message.text || '')) + '#' + message.timecreated + '#' +
@ -158,7 +163,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
*
* @param hash Hash of the message to be removed.
*/
protected removeMessage(hash: any): void {
protected removeMessage(hash: string): void {
if (this.keepMessageMap[hash]) {
// Selected to keep it, clear the flag.
this.keepMessageMap[hash] = false;
@ -261,10 +266,11 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
if (!this.title && this.messages.length) {
// Didn't receive the fullname via argument. Try to get it from messages.
// It's possible that name cannot be resolved when no messages were yet exchanged.
if (this.messages[0].useridto != this.currentUserId) {
this.title = this.messages[0].usertofullname || '';
const firstMessage = <AddonMessagesGetMessagesMessageFormatted> this.messages[0];
if (firstMessage.useridto != this.currentUserId) {
this.title = firstMessage.usertofullname || '';
} else {
this.title = this.messages[0].userfromfullname || '';
this.title = firstMessage.userfromfullname || '';
}
}
});
@ -302,7 +308,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
*
* @return Resolved when done.
*/
protected fetchMessages(): Promise<any> {
protected fetchMessages(): Promise<void> {
this.loadMoreError = false;
if (this.messagesBeingSent > 0) {
@ -341,7 +347,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
return this.getDiscussionMessages(this.pagesLoaded);
});
}
}).then((messages) => {
}).then((messages: (AddonMessagesConversationMessageFormatted | AddonMessagesGetMessagesMessageFormatted)[]) => {
this.loadMessages(messages);
}).finally(() => {
this.fetching = false;
@ -353,7 +359,9 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
*
* @param messages Messages to load.
*/
protected loadMessages(messages: any[]): void {
protected loadMessages(messages: (AddonMessagesConversationMessageFormatted | AddonMessagesGetMessagesMessageFormatted)[])
: void {
if (this.viewDestroyed) {
return;
}
@ -382,7 +390,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
this.messagesProvider.sortMessages(this.messages);
// Calculate which messages need to display the date or user data.
this.messages.forEach((message, index): any => {
this.messages.forEach((message, index) => {
message.showDate = this.showDate(message, this.messages[index - 1]);
message.showUserData = this.showUserData(message, this.messages[index - 1]);
message.showTail = this.showTail(message, this.messages[index + 1]);
@ -411,20 +419,22 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
* @return Promise resolved with a boolean: whether the conversation exists or not.
*/
protected getConversation(conversationId: number, userId: number): Promise<boolean> {
let promise,
fallbackConversation;
let promise: Promise<number>,
fallbackConversation: AddonMessagesConversationFormatted;
// Try to get the conversationId if we don't have it.
if (conversationId) {
promise = Promise.resolve(conversationId);
} else {
let subPromise: Promise<AddonMessagesConversationFormatted>;
if (userId == this.currentUserId && this.messagesProvider.isSelfConversationEnabled()) {
promise = this.messagesProvider.getSelfConversation();
subPromise = this.messagesProvider.getSelfConversation();
} else {
promise = this.messagesProvider.getConversationBetweenUsers(userId, undefined, true);
subPromise = this.messagesProvider.getConversationBetweenUsers(userId, undefined, true);
}
promise = promise.then((conversation) => {
promise = subPromise.then((conversation) => {
fallbackConversation = conversation;
return conversation.id;
@ -437,14 +447,14 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
// Ignore errors.
}).then(() => {
return this.messagesProvider.getConversation(conversationId, undefined, true);
}).catch((error) => {
}).catch((error): any => {
// Get conversation failed, use the fallback one if we have it.
if (fallbackConversation) {
return fallbackConversation;
}
return Promise.reject(error);
}).then((conversation) => {
}).then((conversation: AddonMessagesConversationFormatted) => {
this.conversation = conversation;
if (conversation) {
@ -495,7 +505,9 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
* @param offset Offset for message list.
* @return Promise resolved with the list of messages.
*/
protected getConversationMessages(pagesToLoad: number, offset: number = 0): Promise<any[]> {
protected getConversationMessages(pagesToLoad: number, offset: number = 0)
: Promise<AddonMessagesConversationMessageFormatted[]> {
const excludePending = offset > 0;
return this.messagesProvider.getConversationMessages(this.conversationId, excludePending, offset).then((result) => {
@ -535,7 +547,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
* @return Resolved when done.
*/
protected getDiscussionMessages(pagesToLoad: number, lfReceivedUnread: number = 0, lfReceivedRead: number = 0,
lfSentUnread: number = 0, lfSentRead: number = 0): Promise<any> {
lfSentUnread: number = 0, lfSentRead: number = 0): Promise<AddonMessagesGetMessagesMessageFormatted[]> {
// Only get offline messages if we're loading the first "page".
const excludePending = lfReceivedUnread > 0 || lfReceivedRead > 0 || lfSentUnread > 0 || lfSentRead > 0;
@ -547,7 +559,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
pagesToLoad--;
if (pagesToLoad > 0 && result.canLoadMore) {
// More pages to load. Calculate new limit froms.
result.messages.forEach((message) => {
result.messages.forEach((message: AddonMessagesGetMessagesMessageFormatted) => {
if (!message.pending) {
if (message.useridfrom == this.userId) {
if (message.read) {
@ -598,7 +610,8 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
for (const x in this.messages) {
const message = this.messages[x];
// If an unread message is found, mark all messages as read.
if (message.useridfrom != this.currentUserId && message.read == 0) {
if (message.useridfrom != this.currentUserId &&
(<AddonMessagesGetMessagesMessageFormatted> message).read == 0) {
messageUnreadFound = true;
break;
}
@ -616,7 +629,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
promise = this.messagesProvider.markAllMessagesRead(this.userId).then(() => {
// Mark all messages as read.
this.messages.forEach((message) => {
message.read = 1;
(<AddonMessagesGetMessagesMessageFormatted> message).read = 1;
});
});
}
@ -630,10 +643,10 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
// Mark each message as read one by one.
this.messages.forEach((message) => {
// If the message is unread, call this.messagesProvider.markMessageRead.
if (message.useridfrom != this.currentUserId && message.read == 0) {
if (message.useridfrom != this.currentUserId && (<AddonMessagesGetMessagesMessageFormatted> message).read == 0) {
promises.push(this.messagesProvider.markMessageRead(message.id).then(() => {
readChanged = true;
message.read = 1;
(<AddonMessagesGetMessagesMessageFormatted> message).read = 1;
}));
}
});
@ -703,7 +716,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
if (!message.pending && message.useridfrom != this.currentUserId) {
found++;
if (found == this.conversation.unreadcount) {
this.unreadMessageFrom = parseInt(message.id, 10);
this.unreadMessageFrom = Number(message.id);
break;
}
}
@ -713,13 +726,13 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
let previousMessageRead = false;
for (const x in this.messages) {
const message = this.messages[x];
const message = <AddonMessagesGetMessagesMessageFormatted> this.messages[x];
if (message.useridfrom != this.currentUserId) {
const unreadFrom = message.read == 0 && previousMessageRead;
if (unreadFrom) {
// Save where the label is placed.
this.unreadMessageFrom = parseInt(message.id, 10);
this.unreadMessageFrom = Number(message.id);
break;
}
@ -808,8 +821,9 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
*
* @param message Message to be copied.
*/
copyMessage(message: any): void {
const text = this.textUtils.decodeHTMLEntities(message.smallmessage || message.text || '');
copyMessage(message: AddonMessagesConversationMessageFormatted | AddonMessagesGetMessagesMessageFormatted): void {
const text = this.textUtils.decodeHTMLEntities(
(<AddonMessagesGetMessagesMessageFormatted> message).smallmessage || message.text || '');
this.utils.copyToClipboard(text);
}
@ -819,7 +833,9 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
* @param message Message object to delete.
* @param index Index where the message is to delete it from the view.
*/
deleteMessage(message: any, index: number): void {
deleteMessage(message: AddonMessagesConversationMessageFormatted | AddonMessagesGetMessagesMessageFormatted, index: number)
: void {
const canDeleteAll = this.conversation && this.conversation.candeletemessagesforallusers,
langKey = message.pending || canDeleteAll || this.isSelf ? 'core.areyousure' :
'addon.messages.deletemessageconfirmation',
@ -860,7 +876,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
* @param infiniteComplete Infinite scroll complete function. Only used from core-infinite-loading.
* @return Resolved when done.
*/
loadPrevious(infiniteComplete?: any): Promise<any> {
loadPrevious(infiniteComplete?: any): Promise<void> {
let infiniteHeight = this.infinite ? this.infinite.getHeight() : 0;
const scrollHeight = this.domUtils.getScrollHeight(this.content);
@ -962,7 +978,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
* @param text Message text.
*/
sendMessage(text: string): void {
let message;
let message: AddonMessagesConversationMessageFormatted | AddonMessagesGetMessagesMessageFormatted;
this.hideUnreadLabel();
@ -970,6 +986,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
this.scrollBottom = true;
message = {
id: null,
pending: true,
sending: true,
useridfrom: this.currentUserId,
@ -985,7 +1002,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
// If there is an ongoing fetch, wait for it to finish.
// Otherwise, if a message is sent while fetching it could disappear until the next fetch.
this.waitForFetch().finally(() => {
let promise;
let promise: Promise<{sent: boolean, message: any}>;
if (this.conversationId) {
promise = this.messagesProvider.sendMessageToConversation(this.conversation, text);
@ -1050,7 +1067,9 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
* @param prevMessage Previous message where to compare the date with.
* @return If date has changed and should be shown.
*/
showDate(message: any, prevMessage?: any): boolean {
showDate(message: AddonMessagesConversationMessageFormatted | AddonMessagesGetMessagesMessageFormatted,
prevMessage?: AddonMessagesConversationMessageFormatted | AddonMessagesGetMessagesMessageFormatted): boolean {
if (!prevMessage) {
// First message, show it.
return true;
@ -1068,7 +1087,9 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
* @param prevMessage Previous message.
* @return Whether user data should be shown.
*/
showUserData(message: any, prevMessage?: any): boolean {
showUserData(message: AddonMessagesConversationMessageFormatted | AddonMessagesGetMessagesMessageFormatted,
prevMessage?: AddonMessagesConversationMessageFormatted | AddonMessagesGetMessagesMessageFormatted): boolean {
return this.isGroup && message.useridfrom != this.currentUserId && this.members[message.useridfrom] &&
(!prevMessage || prevMessage.useridfrom != message.useridfrom || message.showDate);
}
@ -1080,7 +1101,8 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
* @param nextMessage Next message.
* @return Whether user data should be shown.
*/
showTail(message: any, nextMessage?: any): boolean {
showTail(message: AddonMessagesConversationMessageFormatted | AddonMessagesGetMessagesMessageFormatted,
nextMessage?: AddonMessagesConversationMessageFormatted | AddonMessagesGetMessagesMessageFormatted): boolean {
return !nextMessage || nextMessage.useridfrom != message.useridfrom || nextMessage.showDate;
}
@ -1422,3 +1444,26 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
this.viewDestroyed = true;
}
}
/**
* Conversation message with some calculated data.
*/
type AddonMessagesConversationMessageFormatted = AddonMessagesConversationMessage & {
pending?: boolean; // Calculated in the app. Whether the message is pending to be sent.
sending?: boolean; // Calculated in the app. Whether the message is being sent right now.
hash?: string; // Calculated in the app. A hash to identify the message.
showDate?: boolean; // Calculated in the app. Whether to show the date before the message.
showUserData?: boolean; // Calculated in the app. Whether to show the user data in the message.
showTail?: boolean; // Calculated in the app. Whether to show a "tail" in the message.
};
/**
* Message with some calculated data.
*/
type AddonMessagesGetMessagesMessageFormatted = AddonMessagesGetMessagesMessage & {
sending?: boolean; // Calculated in the app. Whether the message is being sent right now.
hash?: string; // Calculated in the app. A hash to identify the message.
showDate?: boolean; // Calculated in the app. Whether to show the date before the message.
showUserData?: boolean; // Calculated in the app. Whether to show the user data in the message.
showTail?: boolean; // Calculated in the app. Whether to show a "tail" in the message.
};

View File

@ -17,7 +17,9 @@ import { IonicPage, Platform, NavController, NavParams, Content } from 'ionic-an
import { TranslateService } from '@ngx-translate/core';
import { CoreEventsProvider } from '@providers/events';
import { CoreSitesProvider } from '@providers/sites';
import { AddonMessagesProvider } from '../../providers/messages';
import {
AddonMessagesProvider, AddonMessagesConversationFormatted, AddonMessagesConversationMessage
} from '../../providers/messages';
import { AddonMessagesOfflineProvider } from '../../providers/messages-offline';
import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { CoreUtilsProvider } from '@providers/utils/utils';
@ -45,19 +47,19 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
selectedConversationId: number;
selectedUserId: number;
contactRequestsCount = 0;
favourites: any = {
favourites: AddonMessagesGroupConversationOption = {
type: null,
favourites: true,
count: 0,
unread: 0
unread: 0,
};
group: any = {
group: AddonMessagesGroupConversationOption = {
type: AddonMessagesProvider.MESSAGE_CONVERSATION_TYPE_GROUP,
favourites: false,
count: 0,
unread: 0
};
individual: any = {
individual: AddonMessagesGroupConversationOption = {
type: AddonMessagesProvider.MESSAGE_CONVERSATION_TYPE_INDIVIDUAL,
favourites: false,
count: 0,
@ -331,7 +333,7 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
*
* @return Promise resolved when done.
*/
protected fetchDataForExpandedOption(): Promise<any> {
protected fetchDataForExpandedOption(): Promise<void> {
const expandedOption = this.getExpandedOption();
if (expandedOption) {
@ -349,12 +351,12 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
* @param getCounts Whether to get counts data.
* @return Promise resolved when done.
*/
fetchDataForOption(option: any, loadingMore?: boolean, getCounts?: boolean): Promise<void> {
fetchDataForOption(option: AddonMessagesGroupConversationOption, loadingMore?: boolean, getCounts?: boolean): Promise<void> {
option.loadMoreError = false;
const limitFrom = loadingMore ? option.conversations.length : 0,
promises = [];
let data,
let data: {conversations: AddonMessagesConversationForList[], canLoadMore: boolean},
offlineMessages;
// Get the conversations and, if needed, the offline messages. Always try to get the latest data.
@ -422,7 +424,9 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
* @param option The option to search in. If not defined, search in all options.
* @return Conversation.
*/
protected findConversation(conversationId: number, userId?: number, option?: any): any {
protected findConversation(conversationId: number, userId?: number, option?: AddonMessagesGroupConversationOption)
: AddonMessagesConversationForList {
if (conversationId) {
const conversations = option ? (option.conversations || []) : ((this.favourites.conversations || [])
.concat(this.group.conversations || []).concat(this.individual.conversations || []));
@ -445,7 +449,7 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
*
* @return Option currently expanded.
*/
protected getExpandedOption(): any {
protected getExpandedOption(): AddonMessagesGroupConversationOption {
if (this.favourites.expanded) {
return this.favourites;
} else if (this.group.expanded) {
@ -495,9 +499,9 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
*
* @param option The option to fetch data for.
* @param infiniteComplete Infinite scroll complete function. Only used from core-infinite-loading.
* @return Resolved when done.
* @return Promise resolved when done.
*/
loadMoreConversations(option: any, infiniteComplete?: any): Promise<any> {
loadMoreConversations(option: AddonMessagesGroupConversationOption, infiniteComplete?: any): Promise<void> {
return this.fetchDataForOption(option, true).catch((error) => {
this.domUtils.showErrorModalDefault(error, 'addon.messages.errorwhileretrievingdiscussions', true);
option.loadMoreError = true;
@ -513,7 +517,7 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
* @param messages Offline messages.
* @return Promise resolved when done.
*/
protected loadOfflineMessages(option: any, messages: any[]): Promise<any> {
protected loadOfflineMessages(option: AddonMessagesGroupConversationOption, messages: any[]): Promise<any> {
const promises = [];
messages.forEach((message) => {
@ -588,7 +592,7 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
* @param conversation Conversation where to put the last message.
* @param message Offline message to add.
*/
protected addLastOfflineMessage(conversation: any, message: any): void {
protected addLastOfflineMessage(conversation: any, message: AddonMessagesConversationMessage): void {
conversation.lastmessage = message.text;
conversation.lastmessagedate = message.timecreated / 1000;
conversation.lastmessagepending = true;
@ -601,7 +605,7 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
* @param conversation Conversation to check.
* @return Option object.
*/
protected getConversationOption(conversation: any): any {
protected getConversationOption(conversation: AddonMessagesConversationForList): AddonMessagesGroupConversationOption {
if (conversation.isfavourite) {
return this.favourites;
} else if (conversation.type == AddonMessagesProvider.MESSAGE_CONVERSATION_TYPE_GROUP) {
@ -618,7 +622,7 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
* @param refreshUnreadCounts Whether to refresh unread counts.
* @return Promise resolved when done.
*/
refreshData(refresher?: any, refreshUnreadCounts: boolean = true): Promise<any> {
refreshData(refresher?: any, refreshUnreadCounts: boolean = true): Promise<void> {
// Don't invalidate conversations and so, they always try to get latest data.
const promises = [
this.messagesProvider.invalidateContactRequestsCountCache(this.siteId)
@ -638,7 +642,7 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
*
* @param option The option to expand/collapse.
*/
toggle(option: any): void {
toggle(option: AddonMessagesGroupConversationOption): void {
if (option.expanded) {
// Already expanded, close it.
option.expanded = false;
@ -658,7 +662,7 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
* @param getCounts Whether to get counts data.
* @return Promise resolved when done.
*/
protected expandOption(option: any, getCounts?: boolean): Promise<any> {
protected expandOption(option: AddonMessagesGroupConversationOption, getCounts?: boolean): Promise<void> {
// Collapse all and expand the right one.
this.favourites.expanded = false;
this.group.expanded = false;
@ -715,3 +719,25 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
this.memberInfoObserver && this.memberInfoObserver.off();
}
}
/**
* Conversation options.
*/
export type AddonMessagesGroupConversationOption = {
type: number; // Option type.
favourites: boolean; // Whether it contains favourites conversations.
count: number; // Number of conversations.
unread?: number; // Number of unread conversations.
expanded?: boolean; // Whether the option is currently expanded.
loading?: boolean; // Whether the option is being loaded.
canLoadMore?: boolean; // Whether it can load more data.
loadMoreError?: boolean; // Whether there was an error loading more conversations.
conversations?: AddonMessagesConversationForList[]; // List of conversations.
};
/**
* Formatted conversation with some calculated data for the list.
*/
export type AddonMessagesConversationForList = AddonMessagesConversationFormatted & {
lastmessagepending?: boolean; // Calculated in the app. Whether last message is pending to be sent.
};

View File

@ -16,7 +16,7 @@ import { Component, OnDestroy, ViewChild } from '@angular/core';
import { IonicPage } from 'ionic-angular';
import { CoreEventsProvider } from '@providers/events';
import { CoreSitesProvider } from '@providers/sites';
import { AddonMessagesProvider } from '../../providers/messages';
import { AddonMessagesProvider, AddonMessagesConversationMember, AddonMessagesMessageAreaContact } from '../../providers/messages';
import { CoreSplitViewComponent } from '@components/split-view/split-view';
import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { CoreAppProvider } from '@providers/app';
@ -38,21 +38,21 @@ export class AddonMessagesSearchPage implements OnDestroy {
contacts = {
type: 'contacts',
titleString: 'addon.messages.contacts',
results: [],
results: <AddonMessagesConversationMember[]> [],
canLoadMore: false,
loadingMore: false
};
nonContacts = {
type: 'noncontacts',
titleString: 'addon.messages.noncontacts',
results: [],
results: <AddonMessagesConversationMember[]> [],
canLoadMore: false,
loadingMore: false
};
messages = {
type: 'messages',
titleString: 'addon.messages.messages',
results: [],
results: <AddonMessagesMessageAreaContact[]> [],
canLoadMore: false,
loadingMore: false,
loadMoreError: false
@ -116,9 +116,9 @@ export class AddonMessagesSearchPage implements OnDestroy {
this.displaySearching = !loadMore;
const promises = [];
let newContacts = [];
let newNonContacts = [];
let newMessages = [];
let newContacts: AddonMessagesConversationMember[] = [];
let newNonContacts: AddonMessagesConversationMember[] = [];
let newMessages: AddonMessagesMessageAreaContact[] = [];
let canLoadMoreContacts = false;
let canLoadMoreNonContacts = false;
let canLoadMoreMessages = false;

View File

@ -14,7 +14,10 @@
import { Component, OnDestroy } from '@angular/core';
import { IonicPage } from 'ionic-angular';
import { AddonMessagesProvider } from '../../providers/messages';
import {
AddonMessagesProvider, AddonMessagesMessagePreferences, AddonMessagesMessagePreferencesNotification,
AddonMessagesMessagePreferencesNotificationProcessor
} from '../../providers/messages';
import { CoreUserProvider } from '@core/user/providers/user';
import { CoreAppProvider } from '@providers/app';
import { CoreConfigProvider } from '@providers/config';
@ -34,7 +37,7 @@ import { CoreConstants } from '@core/constants';
export class AddonMessagesSettingsPage implements OnDestroy {
protected updateTimeout: any;
preferences: any;
preferences: AddonMessagesMessagePreferences;
preferencesLoaded: boolean;
contactablePrivacy: number | boolean;
advancedContactable = false; // Whether the site supports "advanced" contactable privacy.
@ -78,9 +81,9 @@ export class AddonMessagesSettingsPage implements OnDestroy {
/**
* Fetches preference data.
*
* @return Resolved when done.
* @return Promise resolved when done.
*/
protected fetchPreferences(): Promise<any> {
protected fetchPreferences(): Promise<void> {
return this.messagesProvider.getMessagePreferences().then((preferences) => {
if (this.groupMessagingEnabled) {
// Simplify the preferences.
@ -90,11 +93,12 @@ export class AddonMessagesSettingsPage implements OnDestroy {
return notification.preferencekey == AddonMessagesProvider.NOTIFICATION_PREFERENCES_KEY;
});
for (const notification of component.notifications) {
for (const processor of notification.processors) {
component.notifications.forEach((notification) => {
notification.processors.forEach(
(processor: AddonMessagesMessagePreferencesNotificationProcessorFormatted) => {
processor.checked = processor.loggedin.checked || processor.loggedoff.checked;
}
}
});
});
}
}
@ -168,14 +172,16 @@ export class AddonMessagesSettingsPage implements OnDestroy {
* @param state State name, ['loggedin', 'loggedoff'].
* @param processor Notification processor.
*/
changePreference(notification: any, state: string, processor: any): void {
changePreference(notification: AddonMessagesMessagePreferencesNotificationFormatted, state: string,
processor: AddonMessagesMessagePreferencesNotificationProcessorFormatted): void {
if (this.groupMessagingEnabled) {
// Update both states at the same time.
const valueArray = [],
promises = [];
let value = 'none';
notification.processors.forEach((processor) => {
notification.processors.forEach((processor: AddonMessagesMessagePreferencesNotificationProcessorFormatted) => {
if (processor.checked) {
valueArray.push(processor.name);
}
@ -268,3 +274,17 @@ export class AddonMessagesSettingsPage implements OnDestroy {
}
}
}
/**
* Message preferences notification with some caclulated data.
*/
type AddonMessagesMessagePreferencesNotificationFormatted = AddonMessagesMessagePreferencesNotification & {
updating?: boolean | {[state: string]: boolean}; // Calculated in the app. Whether the notification is being updated.
};
/**
* Message preferences notification processor with some caclulated data.
*/
type AddonMessagesMessagePreferencesNotificationProcessorFormatted = AddonMessagesMessagePreferencesNotificationProcessor & {
checked?: boolean; // Calculated in the app. Whether the processor is checked either for loggedin or loggedoff.
};

View File

@ -248,7 +248,7 @@ export class AddonMessagesMainMenuHandler implements CoreMainMenuHandler, CoreCr
}
const currentUserId = site.getUserId(),
message = conv.messages[0]; // Treat only the last message, is the one we're interested.
message: any = conv.messages[0]; // Treat only the last message, is the one we're interested.
if (!message || message.useridfrom == currentUserId) {
// No last message or not from current user. Return empty list.

View File

@ -23,6 +23,7 @@ import { CoreTimeUtilsProvider } from '@providers/utils/time';
import { CoreEmulatorHelperProvider } from '@core/emulator/providers/helper';
import { CoreEventsProvider } from '@providers/events';
import { CoreSite } from '@classes/site';
import { CoreWSExternalWarning } from '@providers/ws';
/**
* Service to handle messages.
@ -89,9 +90,9 @@ export class AddonMessagesProvider {
*
* @param userId User ID of the person to block.
* @param siteId Site ID. If not defined, use current site.
* @return Resolved when done.
* @return Promise resolved when done.
*/
blockContact(userId: number, siteId?: string): Promise<any> {
blockContact(userId: number, siteId?: string): Promise<void> {
return this.sitesProvider.getSite(siteId).then((site) => {
let promise;
if (site.wsAvailable('core_message_block_user')) {
@ -313,7 +314,9 @@ export class AddonMessagesProvider {
* @param userId User ID viewing the conversation.
* @return Formatted conversation.
*/
protected formatConversation(conversation: any, userId: number): any {
protected formatConversation(conversation: AddonMessagesConversationFormatted, userId: number)
: AddonMessagesConversationFormatted {
const numMessages = conversation.messages.length,
lastMessage = numMessages ? conversation.messages[numMessages - 1] : null;
@ -536,10 +539,10 @@ export class AddonMessagesProvider {
* Get all the contacts of the current user.
*
* @param siteId Site ID. If not defined, use current site.
* @return Resolved with the WS data.
* @return Promise resolved with the WS data.
* @deprecated since Moodle 3.6
*/
getAllContacts(siteId?: string): Promise<any> {
getAllContacts(siteId?: string): Promise<AddonMessagesGetContactsResult> {
siteId = siteId || this.sitesProvider.getCurrentSiteId();
return this.getContacts(siteId).then((contacts) => {
@ -562,9 +565,9 @@ export class AddonMessagesProvider {
* Get all the users blocked by the current user.
*
* @param siteId Site ID. If not defined, use current site.
* @return Resolved with the WS data.
* @return Promise resolved with the WS data.
*/
getBlockedContacts(siteId?: string): Promise<any> {
getBlockedContacts(siteId?: string): Promise<AddonMessagesGetBlockedUsersResult> {
return this.sitesProvider.getSite(siteId).then((site) => {
const userId = site.getUserId(),
params = {
@ -585,19 +588,24 @@ export class AddonMessagesProvider {
* This excludes the blocked users.
*
* @param siteId Site ID. If not defined, use current site.
* @return Resolved with the WS data.
* @return Promise resolved with the WS data.
* @deprecated since Moodle 3.6
*/
getContacts(siteId?: string): Promise<any> {
getContacts(siteId?: string): Promise<AddonMessagesGetContactsResult> {
return this.sitesProvider.getSite(siteId).then((site) => {
const preSets = {
cacheKey: this.getCacheKeyForContacts(),
updateFrequency: CoreSite.FREQUENCY_OFTEN
};
return site.read('core_message_get_contacts', undefined, preSets).then((contacts) => {
return site.read('core_message_get_contacts', undefined, preSets).then((contacts: AddonMessagesGetContactsResult) => {
// Filter contacts with negative ID, they are notifications.
const validContacts = {};
const validContacts: AddonMessagesGetContactsResult = {
online: [],
offline: [],
strangers: []
};
for (const typeName in contacts) {
if (!validContacts[typeName]) {
validContacts[typeName] = [];
@ -621,11 +629,11 @@ export class AddonMessagesProvider {
* @param limitFrom Position of the first contact to fetch.
* @param limitNum Number of contacts to fetch. Default is AddonMessagesProvider.LIMIT_CONTACTS.
* @param siteId Site ID. If not defined, use current site.
* @return Resolved with the list of user contacts.
* @return Promise resolved with the list of user contacts.
* @since 3.6
*/
getUserContacts(limitFrom: number = 0, limitNum: number = AddonMessagesProvider.LIMIT_CONTACTS , siteId?: string):
Promise<{contacts: any[], canLoadMore: boolean}> {
Promise<{contacts: AddonMessagesConversationMember[], canLoadMore: boolean}> {
return this.sitesProvider.getSite(siteId).then((site) => {
const params = {
@ -638,7 +646,9 @@ export class AddonMessagesProvider {
updateFrequency: CoreSite.FREQUENCY_OFTEN
};
return site.read('core_message_get_user_contacts', params, preSets).then((contacts) => {
return site.read('core_message_get_user_contacts', params, preSets)
.then((contacts: AddonMessagesConversationMember[]) => {
if (!contacts || !contacts.length) {
return { contacts: [], canLoadMore: false };
}
@ -663,11 +673,11 @@ export class AddonMessagesProvider {
* @param limitFrom Position of the first contact request to fetch.
* @param limitNum Number of contact requests to fetch. Default is AddonMessagesProvider.LIMIT_CONTACTS.
* @param siteId Site ID. If not defined, use current site.
* @return Resolved with the list of contact requests.
* @return Promise resolved with the list of contact requests.
* @since 3.6
*/
getContactRequests(limitFrom: number = 0, limitNum: number = AddonMessagesProvider.LIMIT_CONTACTS, siteId?: string):
Promise<{requests: any[], canLoadMore: boolean}> {
Promise<{requests: AddonMessagesConversationMember[], canLoadMore: boolean}> {
return this.sitesProvider.getSite(siteId).then((site) => {
const data = {
@ -680,7 +690,9 @@ export class AddonMessagesProvider {
updateFrequency: CoreSite.FREQUENCY_OFTEN
};
return site.read('core_message_get_contact_requests', data, preSets).then((requests) => {
return site.read('core_message_get_contact_requests', data, preSets)
.then((requests: AddonMessagesConversationMember[]) => {
if (!requests || !requests.length) {
return { requests: [], canLoadMore: false };
}
@ -716,7 +728,7 @@ export class AddonMessagesProvider {
typeExpected: 'number'
};
return site.read('core_message_get_received_contact_requests_count', data, preSets).then((count) => {
return site.read('core_message_get_received_contact_requests_count', data, preSets).then((count: number) => {
// Notify the new count so all badges are updated.
this.eventsProvider.trigger(AddonMessagesProvider.CONTACT_REQUESTS_COUNT_EVENT, { count }, site.id);
@ -745,7 +757,7 @@ export class AddonMessagesProvider {
*/
getConversation(conversationId: number, includeContactRequests?: boolean, includePrivacyInfo?: boolean,
messageOffset: number = 0, messageLimit: number = 1, memberOffset: number = 0, memberLimit: number = 2,
newestFirst: boolean = true, siteId?: string, userId?: number): Promise<any> {
newestFirst: boolean = true, siteId?: string, userId?: number): Promise<AddonMessagesConversationFormatted> {
return this.sitesProvider.getSite(siteId).then((site) => {
userId = userId || site.getUserId();
@ -765,7 +777,7 @@ export class AddonMessagesProvider {
newestmessagesfirst: newestFirst ? 1 : 0
};
return site.read('core_message_get_conversation', params, preSets).then((conversation) => {
return site.read('core_message_get_conversation', params, preSets).then((conversation: AddonMessagesConversation) => {
return this.formatConversation(conversation, userId);
});
});
@ -792,7 +804,8 @@ export class AddonMessagesProvider {
*/
getConversationBetweenUsers(otherUserId: number, includeContactRequests?: boolean, includePrivacyInfo?: boolean,
messageOffset: number = 0, messageLimit: number = 1, memberOffset: number = 0, memberLimit: number = 2,
newestFirst: boolean = true, siteId?: string, userId?: number, preferCache?: boolean): Promise<any> {
newestFirst: boolean = true, siteId?: string, userId?: number, preferCache?: boolean)
: Promise<AddonMessagesConversationFormatted> {
return this.sitesProvider.getSite(siteId).then((site) => {
userId = userId || site.getUserId();
@ -813,7 +826,8 @@ export class AddonMessagesProvider {
newestmessagesfirst: newestFirst ? 1 : 0
};
return site.read('core_message_get_conversation_between_users', params, preSets).then((conversation) => {
return site.read('core_message_get_conversation_between_users', params, preSets)
.then((conversation: AddonMessagesConversation) => {
return this.formatConversation(conversation, userId);
});
});
@ -826,12 +840,11 @@ export class AddonMessagesProvider {
* @param limitFrom Offset for members list.
* @param limitTo Limit of members.
* @param siteId Site ID. If not defined, use current site.
* @param userId User ID. If not defined, current user in the site.
* @return Promise resolved with the response.
* @param userId User ID. If not defined, current user in
* @since 3.6
*/
getConversationMembers(conversationId: number, limitFrom: number = 0, limitTo?: number, includeContactRequests?: boolean,
siteId?: string, userId?: number): Promise<any> {
siteId?: string, userId?: number): Promise<{members: AddonMessagesConversationMember[], canLoadMore: boolean}> {
return this.sitesProvider.getSite(siteId).then((site) => {
userId = userId || site.getUserId();
@ -853,18 +866,21 @@ export class AddonMessagesProvider {
includeprivacyinfo: 1,
};
return site.read('core_message_get_conversation_members', params, preSets).then((members) => {
const result: any = {};
return site.read('core_message_get_conversation_members', params, preSets)
.then((members: AddonMessagesConversationMember[]) => {
if (limitTo < 1) {
result.canLoadMore = false;
result.members = members;
return {
canLoadMore: false,
members: members
};
} else {
result.canLoadMore = members.length > limitTo;
result.members = members.slice(0, limitTo);
return {
canLoadMore: members.length > limitTo,
members: members.slice(0, limitTo)
};
}
return result;
});
});
}
@ -884,7 +900,8 @@ export class AddonMessagesProvider {
* @since 3.6
*/
getConversationMessages(conversationId: number, excludePending: boolean, limitFrom: number = 0, limitTo?: number,
newestFirst: boolean = true, timeFrom: number = 0, siteId?: string, userId?: number): Promise<any> {
newestFirst: boolean = true, timeFrom: number = 0, siteId?: string, userId?: number)
: Promise<AddonMessagesGetConversationMessagesResult> {
return this.sitesProvider.getSite(siteId).then((site) => {
userId = userId || site.getUserId();
@ -913,7 +930,9 @@ export class AddonMessagesProvider {
preSets['emergencyCache'] = false;
}
return site.read('core_message_get_conversation_messages', params, preSets).then((result) => {
return site.read('core_message_get_conversation_messages', params, preSets)
.then((result: AddonMessagesGetConversationMessagesResult) => {
if (limitTo < 1) {
result.canLoadMore = false;
result.messages = result.messages;
@ -975,7 +994,8 @@ export class AddonMessagesProvider {
* @since 3.6
*/
getConversations(type?: number, favourites?: boolean, limitFrom: number = 0, siteId?: string, userId?: number,
forceCache?: boolean, ignoreCache?: boolean): Promise<{conversations: any[], canLoadMore: boolean}> {
forceCache?: boolean, ignoreCache?: boolean)
: Promise<{conversations: AddonMessagesConversationFormatted[], canLoadMore: boolean}> {
return this.sitesProvider.getSite(siteId).then((site) => {
userId = userId || site.getUserId();
@ -1017,7 +1037,7 @@ export class AddonMessagesProvider {
}
return Promise.reject(error);
}).then((response) => {
}).then((response: AddonMessagesGetConversationsResult) => {
// Format the conversations, adding some calculated fields.
const conversations = response.conversations.slice(0, this.LIMIT_MESSAGES).map((conversation) => {
return this.formatConversation(conversation, userId);
@ -1053,7 +1073,9 @@ export class AddonMessagesProvider {
cacheKey: this.getCacheKeyForConversationCounts()
};
return site.read('core_message_get_conversation_counts', {}, preSets).then((result) => {
return site.read('core_message_get_conversation_counts', {}, preSets)
.then((result: AddonMessagesGetConversationCountsResult) => {
const counts = {
favourites: result.favourites,
individual: result.types[AddonMessagesProvider.MESSAGE_CONVERSATION_TYPE_INDIVIDUAL],
@ -1080,10 +1102,14 @@ export class AddonMessagesProvider {
* @return Promise resolved with messages and a boolean telling if can load more messages.
*/
getDiscussion(userId: number, excludePending: boolean, lfReceivedUnread: number = 0, lfReceivedRead: number = 0,
lfSentUnread: number = 0, lfSentRead: number = 0, toDisplay: boolean = true, siteId?: string): Promise<any> {
lfSentUnread: number = 0, lfSentRead: number = 0, toDisplay: boolean = true, siteId?: string)
: Promise<{messages: AddonMessagesGetMessagesMessage[], canLoadMore: boolean}> {
return this.sitesProvider.getSite(siteId).then((site) => {
const result = {},
const result = {
messages: <AddonMessagesGetMessagesMessage[]> [],
canLoadMore: false
},
preSets = {
cacheKey: this.getCacheKeyForDiscussion(userId)
},
@ -1107,7 +1133,7 @@ export class AddonMessagesProvider {
// Get message received by current user.
return this.getRecentMessages(params, preSets, lfReceivedUnread, lfReceivedRead, toDisplay, site.getId())
.then((response) => {
result['messages'] = response;
result.messages = response;
params.useridto = userId;
params.useridfrom = site.getUserId();
hasReceived = response.length > 0;
@ -1115,16 +1141,16 @@ export class AddonMessagesProvider {
// Get message sent by current user.
return this.getRecentMessages(params, preSets, lfSentUnread, lfSentRead, toDisplay, siteId);
}).then((response) => {
result['messages'] = result['messages'].concat(response);
result.messages = result.messages.concat(response);
hasSent = response.length > 0;
if (result['messages'].length > this.LIMIT_MESSAGES) {
if (result.messages.length > this.LIMIT_MESSAGES) {
// Sort messages and get the more recent ones.
result['canLoadMore'] = true;
result['messages'] = this.sortMessages(result['messages']);
result['messages'] = result['messages'].slice(-this.LIMIT_MESSAGES);
result.canLoadMore = true;
result.messages = this.sortMessages(result['messages']);
result.messages = result.messages.slice(-this.LIMIT_MESSAGES);
} else {
result['canLoadMore'] = result['messages'].length == this.LIMIT_MESSAGES && (!hasReceived || !hasSent);
result.canLoadMore = result.messages.length == this.LIMIT_MESSAGES && (!hasReceived || !hasSent);
}
if (excludePending) {
@ -1140,7 +1166,7 @@ export class AddonMessagesProvider {
message.text = message.smallmessage;
});
result['messages'] = result['messages'].concat(offlineMessages);
result.messages = result.messages.concat(offlineMessages);
return result;
});
@ -1153,11 +1179,11 @@ export class AddonMessagesProvider {
* If the site is 3.6 or higher, please use getConversations.
*
* @param siteId Site ID. If not defined, current site.
* @return Resolved with an object where the keys are the user ID of the other user.
* @return Promise resolved with an object where the keys are the user ID of the other user.
*/
getDiscussions(siteId?: string): Promise<any> {
getDiscussions(siteId?: string): Promise<{[userId: number]: AddonMessagesDiscussion}> {
return this.sitesProvider.getSite(siteId).then((site) => {
const discussions = {},
const discussions: {[userId: number]: AddonMessagesDiscussion} = {},
currentUserId = site.getUserId(),
params = {
useridto: currentUserId,
@ -1171,7 +1197,7 @@ export class AddonMessagesProvider {
/**
* Convenience function to treat a recent message, adding it to discussions list if needed.
*/
const treatRecentMessage = (message: any, userId: number, userFullname: string): void => {
const treatRecentMessage = (message: AddonMessagesGetMessagesMessage, userId: number, userFullname: string): void => {
if (typeof discussions[userId] === 'undefined') {
discussions[userId] = {
fullname: userFullname,
@ -1272,7 +1298,7 @@ export class AddonMessagesProvider {
* @return Promise resolved with the member info.
* @since 3.6
*/
getMemberInfo(otherUserId: number, siteId?: string, userId?: number): Promise<any> {
getMemberInfo(otherUserId: number, siteId?: string, userId?: number): Promise<AddonMessagesConversationMember> {
return this.sitesProvider.getSite(siteId).then((site) => {
userId = userId || site.getUserId();
@ -1287,7 +1313,9 @@ export class AddonMessagesProvider {
includeprivacyinfo: 1,
};
return site.read('core_message_get_member_info', params, preSets).then((members) => {
return site.read('core_message_get_member_info', params, preSets)
.then((members: AddonMessagesConversationMember[]): any => {
if (!members || members.length < 1) {
// Should never happen.
return Promise.reject(null);
@ -1313,7 +1341,7 @@ export class AddonMessagesProvider {
* @param siteId Site ID. If not defined, use current site.
* @return Promise resolved with the message preferences.
*/
getMessagePreferences(siteId?: string): Promise<any> {
getMessagePreferences(siteId?: string): Promise<AddonMessagesMessagePreferences> {
this.logger.debug('Get message preferences');
return this.sitesProvider.getSite(siteId).then((site) => {
@ -1322,7 +1350,9 @@ export class AddonMessagesProvider {
updateFrequency: CoreSite.FREQUENCY_SOMETIMES
};
return site.read('core_message_get_user_message_preferences', {}, preSets).then((data) => {
return site.read('core_message_get_user_message_preferences', {}, preSets)
.then((data: AddonMessagesGetUserMessagePreferencesResult): any => {
if (data.preferences) {
data.preferences.blocknoncontacts = data.blocknoncontacts;
@ -1341,15 +1371,18 @@ export class AddonMessagesProvider {
* @param preSets Set of presets for the WS.
* @param toDisplay True if messages will be displayed to the user, either in view or in a notification.
* @param siteId Site ID. If not defined, use current site.
* @return Promise resolved with the data.
*/
protected getMessages(params: any, preSets: any, toDisplay: boolean = true, siteId?: string): Promise<any> {
protected getMessages(params: any, preSets: any, toDisplay: boolean = true, siteId?: string)
: Promise<AddonMessagesGetMessagesResult> {
params['type'] = 'conversations';
params['newestfirst'] = 1;
return this.sitesProvider.getSite(siteId).then((site) => {
const userId = site.getUserId();
return site.read('core_message_get_messages', params, preSets).then((response) => {
return site.read('core_message_get_messages', params, preSets).then((response: AddonMessagesGetMessagesResult) => {
response.messages.forEach((message) => {
message.read = params.read == 0 ? 0 : 1;
// Convert times to milliseconds.
@ -1377,9 +1410,10 @@ export class AddonMessagesProvider {
* @param limitFromRead Number of unread messages already fetched, so fetch will be done from this number.
* @param toDisplay True if messages will be displayed to the user, either in view or in a notification.
* @param siteId Site ID. If not defined, use current site.
* @return Promise resolved with the data.
*/
protected getRecentMessages(params: any, preSets: any, limitFromUnread: number = 0, limitFromRead: number = 0,
toDisplay: boolean = true, siteId?: string): Promise<any> {
toDisplay: boolean = true, siteId?: string): Promise<AddonMessagesGetMessagesMessage[]> {
limitFromUnread = limitFromUnread || 0;
limitFromRead = limitFromRead || 0;
@ -1427,7 +1461,7 @@ export class AddonMessagesProvider {
* @since 3.7
*/
getSelfConversation(messageOffset: number = 0, messageLimit: number = 1, newestFirst: boolean = true, siteId?: string,
userId?: number): Promise<any> {
userId?: number): Promise<AddonMessagesConversationFormatted> {
return this.sitesProvider.getSite(siteId).then((site) => {
userId = userId || site.getUserId();
@ -1442,7 +1476,8 @@ export class AddonMessagesProvider {
newestmessagesfirst: newestFirst ? 1 : 0
};
return site.read('core_message_get_self_conversation', params, preSets).then((conversation) => {
return site.read('core_message_get_self_conversation', params, preSets)
.then((conversation: AddonMessagesConversation) => {
return this.formatConversation(conversation, userId);
});
});
@ -1466,7 +1501,8 @@ export class AddonMessagesProvider {
cacheKey: this.getCacheKeyForUnreadConversationCounts()
};
promise = site.read('core_message_get_unread_conversation_counts', {}, preSets).then((result) => {
promise = site.read('core_message_get_unread_conversation_counts', {}, preSets)
.then((result: AddonMessagesGetUnreadConversationCountsResult) => {
return {
favourites: result.favourites,
individual: result.types[AddonMessagesProvider.MESSAGE_CONVERSATION_TYPE_INDIVIDUAL],
@ -1485,7 +1521,7 @@ export class AddonMessagesProvider {
typeExpected: 'number'
};
promise = site.read('core_message_get_unread_conversations_count', params, preSets).then((count) => {
promise = site.read('core_message_get_unread_conversations_count', params, preSets).then((count: number) => {
return { favourites: 0, individual: count, group: 0, self: 0 };
});
} else {
@ -1536,7 +1572,7 @@ export class AddonMessagesProvider {
* @return Promise resolved with the message unread count.
*/
getUnreadReceivedMessages(toDisplay: boolean = true, forceCache: boolean = false, ignoreCache: boolean = false,
siteId?: string): Promise<any> {
siteId?: string): Promise<AddonMessagesGetMessagesResult> {
return this.sitesProvider.getSite(siteId).then((site) => {
const params = {
read: 0,
@ -2049,7 +2085,7 @@ export class AddonMessagesProvider {
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved with boolean marking success or not.
*/
markMessageRead(messageId: number, siteId?: string): Promise<any> {
markMessageRead(messageId: number, siteId?: string): Promise<AddonMessagesMarkMessageReadResult> {
return this.sitesProvider.getSite(siteId).then((site) => {
const params = {
messageid: messageId,
@ -2067,7 +2103,7 @@ export class AddonMessagesProvider {
* @return Promise resolved if success.
* @since 3.6
*/
markAllConversationMessagesRead(conversationId?: number): Promise<any> {
markAllConversationMessagesRead(conversationId?: number): Promise<null> {
const params = {
userid: this.sitesProvider.getCurrentSiteUserId(),
conversationid: conversationId
@ -2085,7 +2121,7 @@ export class AddonMessagesProvider {
* @param userIdFrom User Id for the sender.
* @return Promise resolved with boolean marking success or not.
*/
markAllMessagesRead(userIdFrom?: number): Promise<any> {
markAllMessagesRead(userIdFrom?: number): Promise<boolean> {
const params = {
useridto: this.sitesProvider.getCurrentSiteUserId(),
useridfrom: userIdFrom
@ -2217,8 +2253,9 @@ export class AddonMessagesProvider {
* @param query The query string.
* @param limit The number of results to return, 0 for none.
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved with the contacts.
*/
searchContacts(query: string, limit: number = 100, siteId?: string): Promise<any> {
searchContacts(query: string, limit: number = 100, siteId?: string): Promise<AddonMessagesSearchContactsContact[]> {
return this.sitesProvider.getSite(siteId).then((site) => {
const data = {
searchtext: query,
@ -2228,7 +2265,9 @@ export class AddonMessagesProvider {
getFromCache: false // Always try to get updated data. If it fails, it will get it from cache.
};
return site.read('core_message_search_contacts', data, preSets).then((contacts) => {
return site.read('core_message_search_contacts', data, preSets)
.then((contacts: AddonMessagesSearchContactsContact[]) => {
if (limit && contacts.length > limit) {
contacts = contacts.splice(0, limit);
}
@ -2250,7 +2289,7 @@ export class AddonMessagesProvider {
* @return Promise resolved with the results.
*/
searchMessages(query: string, userId?: number, limitFrom: number = 0, limitNum: number = AddonMessagesProvider.LIMIT_SEARCH,
siteId?: string): Promise<{messages: any[], canLoadMore: boolean}> {
siteId?: string): Promise<{messages: AddonMessagesMessageAreaContact[], canLoadMore: boolean}> {
return this.sitesProvider.getSite(siteId).then((site) => {
const params = {
@ -2263,13 +2302,15 @@ export class AddonMessagesProvider {
getFromCache: false // Always try to get updated data. If it fails, it will get it from cache.
};
return site.read('core_message_data_for_messagearea_search_messages', params, preSets).then((result) => {
return site.read('core_message_data_for_messagearea_search_messages', params, preSets)
.then((result: AddonMessagesDataForMessageAreaSearchMessagesResult) => {
if (!result.contacts || !result.contacts.length) {
return { messages: [], canLoadMore: false };
}
result.contacts.forEach((result) => {
result.id = result.userid;
result.contacts.forEach((contact) => {
contact.id = contact.userid;
});
this.userProvider.storeUsers(result.contacts, site.id);
@ -2297,7 +2338,8 @@ export class AddonMessagesProvider {
* @since 3.6
*/
searchUsers(query: string, limitFrom: number = 0, limitNum: number = AddonMessagesProvider.LIMIT_SEARCH, siteId?: string):
Promise<{contacts: any[], nonContacts: any[], canLoadMoreContacts: boolean, canLoadMoreNonContacts: boolean}> {
Promise<{contacts: AddonMessagesConversationMember[], nonContacts: AddonMessagesConversationMember[],
canLoadMoreContacts: boolean, canLoadMoreNonContacts: boolean}> {
return this.sitesProvider.getSite(siteId).then((site) => {
const data = {
@ -2310,7 +2352,7 @@ export class AddonMessagesProvider {
getFromCache: false // Always try to get updated data. If it fails, it will get it from cache.
};
return site.read('core_message_message_search_users', data, preSets).then((result) => {
return site.read('core_message_message_search_users', data, preSets).then((result: AddonMessagesSearchUsersResult) => {
const contacts = result.contacts || [];
const nonContacts = result.noncontacts || [];
@ -2341,7 +2383,9 @@ export class AddonMessagesProvider {
* - sent (Boolean) True if message was sent to server, false if stored in device.
* - message (Object) If sent=false, contains the stored message.
*/
sendMessage(toUserId: number, message: string, siteId?: string): Promise<any> {
sendMessage(toUserId: number, message: string, siteId?: string)
: Promise<{sent: boolean, message: AddonMessagesSendInstantMessagesMessage}> {
// Convenience function to store a message to be synchronized later.
const storeOffline = (): Promise<any> => {
return this.messagesOffline.saveMessage(toUserId, message, siteId).then((entry) => {
@ -2395,7 +2439,7 @@ export class AddonMessagesProvider {
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved if success, rejected if failure.
*/
sendMessageOnline(toUserId: number, message: string, siteId?: string): Promise<any> {
sendMessageOnline(toUserId: number, message: string, siteId?: string): Promise<AddonMessagesSendInstantMessagesMessage> {
siteId = siteId || this.sitesProvider.getCurrentSiteId();
const messages = [
@ -2430,7 +2474,7 @@ export class AddonMessagesProvider {
* @return Promise resolved if success, rejected if failure. Promise resolved doesn't mean that messages
* have been sent, the resolve param can contain errors for messages not sent.
*/
sendMessagesOnline(messages: any, siteId?: string): Promise<any> {
sendMessagesOnline(messages: any[], siteId?: string): Promise<AddonMessagesSendInstantMessagesMessage[]> {
return this.sitesProvider.getSite(siteId).then((site) => {
const data = {
messages: messages
@ -2451,7 +2495,9 @@ export class AddonMessagesProvider {
* - message (any) If sent=false, contains the stored message.
* @since 3.6
*/
sendMessageToConversation(conversation: any, message: string, siteId?: string): Promise<any> {
sendMessageToConversation(conversation: any, message: string, siteId?: string)
: Promise<{sent: boolean, message: AddonMessagesSendMessagesToConversationMessage}> {
// Convenience function to store a message to be synchronized later.
const storeOffline = (): Promise<any> => {
return this.messagesOffline.saveConversationMessage(conversation, message, siteId).then((entry) => {
@ -2506,7 +2552,8 @@ export class AddonMessagesProvider {
* @return Promise resolved if success, rejected if failure.
* @since 3.6
*/
sendMessageToConversationOnline(conversationId: number, message: string, siteId?: string): Promise<any> {
sendMessageToConversationOnline(conversationId: number, message: string, siteId?: string)
: Promise<AddonMessagesSendMessagesToConversationMessage> {
siteId = siteId || this.sitesProvider.getCurrentSiteId();
const messages = [
@ -2534,7 +2581,9 @@ export class AddonMessagesProvider {
* @return Promise resolved if success, rejected if failure.
* @since 3.6
*/
sendMessagesToConversationOnline(conversationId: number, messages: any, siteId?: string): Promise<any> {
sendMessagesToConversationOnline(conversationId: number, messages: any[], siteId?: string)
: Promise<AddonMessagesSendMessagesToConversationMessage[]> {
return this.sitesProvider.getSite(siteId).then((site) => {
const params = {
conversationid: conversationId,
@ -2603,10 +2652,10 @@ export class AddonMessagesProvider {
* @param conversations Array of conversations.
* @return Conversations sorted with most recent last.
*/
sortConversations(conversations: any[]): any[] {
sortConversations(conversations: AddonMessagesConversationFormatted[]): AddonMessagesConversationFormatted[] {
return conversations.sort((a, b) => {
const timeA = parseInt(a.lastmessagedate, 10),
timeB = parseInt(b.lastmessagedate, 10);
const timeA = Number(a.lastmessagedate),
timeB = Number(b.lastmessagedate);
if (timeA == timeB && a.id) {
// Same time, sort by ID.
@ -2651,7 +2700,9 @@ export class AddonMessagesProvider {
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved when done.
*/
protected storeLastReceivedMessageIfNeeded(convIdOrUserIdFrom: number, message: any, siteId?: string): Promise<any> {
protected storeLastReceivedMessageIfNeeded(convIdOrUserIdFrom: number,
message: AddonMessagesGetMessagesMessage | AddonMessagesConversationMessage, siteId?: string): Promise<any> {
const component = AddonMessagesProvider.PUSH_SIMULATION_COMPONENT;
// Get the last received message.
@ -2675,7 +2726,7 @@ export class AddonMessagesProvider {
*
* @param contactTypes List of contacts grouped in types.
*/
protected storeUsersFromAllContacts(contactTypes: any): void {
protected storeUsersFromAllContacts(contactTypes: AddonMessagesGetContactsResult): void {
for (const x in contactTypes) {
this.userProvider.storeUsers(contactTypes[x]);
}
@ -2735,3 +2786,377 @@ export class AddonMessagesProvider {
});
}
}
/**
* Conversation.
*/
export type AddonMessagesConversation = {
id: number; // The conversation id.
name: string; // The conversation name, if set.
subname: string; // A subtitle for the conversation name, if set.
imageurl: string; // A link to the conversation picture, if set.
type: number; // The type of the conversation (1=individual,2=group,3=self).
membercount: number; // Total number of conversation members.
ismuted: boolean; // If the user muted this conversation.
isfavourite: boolean; // If the user marked this conversation as a favourite.
isread: boolean; // If the user has read all messages in the conversation.
unreadcount: number; // The number of unread messages in this conversation.
members: AddonMessagesConversationMember[];
messages: AddonMessagesConversationMessage[];
candeletemessagesforallusers: boolean; // @since 3.7. If the user can delete messages in the conversation for all users.
};
/**
* Conversation with some calculated data.
*/
export type AddonMessagesConversationFormatted = AddonMessagesConversation & {
lastmessage?: string; // Calculated in the app. Last message.
lastmessagedate?: number; // Calculated in the app. Date the last message was sent.
sentfromcurrentuser?: boolean; // Calculated in the app. Whether last message was sent by the current user.
name?: string; // Calculated in the app. If private conversation, name of the other user.
userid?: number; // Calculated in the app. URL. If private conversation, ID of the other user.
showonlinestatus?: boolean; // Calculated in the app. If private conversation, whether to show online status of the other user.
isonline?: boolean; // Calculated in the app. If private conversation, whether the other user is online.
isblocked?: boolean; // Calculated in the app. If private conversation, whether the other user is blocked.
otherUser?: AddonMessagesConversationMember; // Calculated in the app. Other user in the conversation.
};
/**
* Conversation member.
*/
export type AddonMessagesConversationMember = {
id: number; // The user id.
fullname: string; // The user's name.
profileurl: string; // The link to the user's profile page.
profileimageurl: string; // User picture URL.
profileimageurlsmall: string; // Small user picture URL.
isonline: boolean; // The user's online status.
showonlinestatus: boolean; // Show the user's online status?.
isblocked: boolean; // If the user has been blocked.
iscontact: boolean; // Is the user a contact?.
isdeleted: boolean; // Is the user deleted?.
canmessageevenifblocked: boolean; // If the user can still message even if they get blocked.
canmessage: boolean; // If the user can be messaged.
requirescontact: boolean; // If the user requires to be contacts.
contactrequests?: { // The contact requests.
id: number; // The id of the contact request.
userid: number; // The id of the user who created the contact request.
requesteduserid: number; // The id of the user confirming the request.
timecreated: number; // The timecreated timestamp for the contact request.
}[];
conversations?: { // Conversations between users.
id: number; // Conversations id.
type: number; // Conversation type: private or public.
name: string; // Multilang compatible conversation name2.
timecreated: number; // The timecreated timestamp for the conversation.
}[];
};
/**
* Conversation message.
*/
export type AddonMessagesConversationMessage = {
id: number; // The id of the message.
useridfrom: number; // The id of the user who sent the message.
text: string; // The text of the message.
timecreated: number; // The timecreated timestamp for the message.
};
/**
* Message preferences.
*/
export type AddonMessagesMessagePreferences = {
userid: number; // User id.
disableall: number; // Whether all the preferences are disabled.
processors: { // Config form values.
displayname: string; // Display name.
name: string; // Processor name.
hassettings: boolean; // Whether has settings.
contextid: number; // Context id.
userconfigured: number; // Whether is configured by the user.
}[];
components: { // Available components.
displayname: string; // Display name.
notifications: AddonMessagesMessagePreferencesNotification[]; // List of notificaitons for the component.
}[];
} & AddonMessagesMessagePreferencesCalculatedData;
/**
* Notification processor in message preferences.
*/
export type AddonMessagesMessagePreferencesNotification = {
displayname: string; // Display name.
preferencekey: string; // Preference key.
processors: AddonMessagesMessagePreferencesNotificationProcessor[]; // Processors values for this notification.
};
/**
* Notification processor in message preferences.
*/
export type AddonMessagesMessagePreferencesNotificationProcessor = {
displayname: string; // Display name.
name: string; // Processor name.
locked: boolean; // Is locked by admin?.
lockedmessage?: string; // Text to display if locked.
userconfigured: number; // Is configured?.
loggedin: {
name: string; // Name.
displayname: string; // Display name.
checked: boolean; // Is checked?.
};
loggedoff: {
name: string; // Name.
displayname: string; // Display name.
checked: boolean; // Is checked?.
};
};
/**
* Message discussion (before 3.6).
*/
export type AddonMessagesDiscussion = {
fullname: string; // Full name of the other user in the discussion.
profileimageurl: string; // Profile image of the other user in the discussion.
message?: { // Last message.
id: number; // Message ID.
user: number; // User ID that sent the message.
message: string; // Text of the message.
timecreated: number; // Time the message was sent.
pending?: boolean; // Whether the message is pending to be sent.
};
unread?: boolean; // Whether the discussion has unread messages.
};
/**
* Contact for message area.
*/
export type AddonMessagesMessageAreaContact = {
userid: number; // The user's id.
fullname: string; // The user's name.
profileimageurl: string; // User picture URL.
profileimageurlsmall: string; // Small user picture URL.
ismessaging: boolean; // If we are messaging the user.
sentfromcurrentuser: boolean; // Was the last message sent from the current user?.
lastmessage: string; // The user's last message.
lastmessagedate: number; // Timestamp for last message.
messageid: number; // The unique search message id.
showonlinestatus: boolean; // Show the user's online status?.
isonline: boolean; // The user's online status.
isread: boolean; // If the user has read the message.
isblocked: boolean; // If the user has been blocked.
unreadcount: number; // The number of unread messages in this conversation.
conversationid: number; // The id of the conversation.
} & AddonMessagesMessageAreaContactCalculatedData;
/**
* Result of WS core_message_get_blocked_users.
*/
export type AddonMessagesGetBlockedUsersResult = {
users: AddonMessagesBlockedUser[]; // List of blocked users.
warnings?: CoreWSExternalWarning[];
};
/**
* User data returned by core_message_get_blocked_users.
*/
export type AddonMessagesBlockedUser = {
id: number; // User ID.
fullname: string; // User full name.
profileimageurl?: string; // User picture URL.
};
/**
* Result of WS core_message_get_contacts.
*/
export type AddonMessagesGetContactsResult = {
online: AddonMessagesGetContactsContact[]; // List of online contacts.
offline: AddonMessagesGetContactsContact[]; // List of offline contacts.
strangers: AddonMessagesGetContactsContact[]; // List of users that are not in the user's contact list but have sent a message.
} & AddonMessagesGetContactsCalculatedData;
/**
* User data returned by core_message_get_contacts.
*/
export type AddonMessagesGetContactsContact = {
id: number; // User ID.
fullname: string; // User full name.
profileimageurl?: string; // User picture URL.
profileimageurlsmall?: string; // Small user picture URL.
unread: number; // Unread message count.
};
/**
* User data returned by core_message_search_contacts.
*/
export type AddonMessagesSearchContactsContact = {
id: number; // User ID.
fullname: string; // User full name.
profileimageurl?: string; // User picture URL.
profileimageurlsmall?: string; // Small user picture URL.
};
/**
* Result of WS core_message_get_conversation_messages.
*/
export type AddonMessagesGetConversationMessagesResult = {
id: number; // The conversation id.
members: AddonMessagesConversationMember[];
messages: AddonMessagesConversationMessage[];
} & AddonMessagesGetConversationMessagesCalculatedData;
/**
* Result of WS core_message_get_conversations.
*/
export type AddonMessagesGetConversationsResult = {
conversations: AddonMessagesConversation[];
};
/**
* Result of WS core_message_get_conversation_counts.
*/
export type AddonMessagesGetConversationCountsResult = {
favourites: number; // Total number of favourite conversations.
types: {
1: number; // Total number of individual conversations.
2: number; // Total number of group conversations.
3: number; // Total number of self conversations.
};
};
/**
* Result of WS core_message_get_unread_conversation_counts.
*/
export type AddonMessagesGetUnreadConversationCountsResult = {
favourites: number; // Total number of unread favourite conversations.
types: {
1: number; // Total number of unread individual conversations.
2: number; // Total number of unread group conversations.
3: number; // Total number of unread self conversations.
};
};
/**
* Result of WS core_message_get_user_message_preferences.
*/
export type AddonMessagesGetUserMessagePreferencesResult = {
preferences: AddonMessagesMessagePreferences;
blocknoncontacts: number; // Privacy messaging setting to define who can message you.
entertosend: boolean; // User preference for using enter to send messages.
warnings?: CoreWSExternalWarning[];
};
/**
* Result of WS core_message_get_messages.
*/
export type AddonMessagesGetMessagesResult = {
messages: AddonMessagesGetMessagesMessage[];
warnings?: CoreWSExternalWarning[];
};
/**
* Message data returned by core_message_get_messages.
*/
export type AddonMessagesGetMessagesMessage = {
id: number; // Message id.
useridfrom: number; // User from id.
useridto: number; // User to id.
subject: string; // The message subject.
text: string; // The message text formated.
fullmessage: string; // The message.
fullmessageformat: number; // Fullmessage format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
fullmessagehtml: string; // The message in html.
smallmessage: string; // The shorten message.
notification: number; // Is a notification?.
contexturl: string; // Context URL.
contexturlname: string; // Context URL link name.
timecreated: number; // Time created.
timeread: number; // Time read.
usertofullname: string; // User to full name.
userfromfullname: string; // User from full name.
component?: string; // The component that generated the notification.
eventtype?: string; // The type of notification.
customdata?: string; // Custom data to be passed to the message processor.
} & AddonMessagesGetMessagesMessageCalculatedData;
/**
* Result of WS core_message_data_for_messagearea_search_messages.
*/
export type AddonMessagesDataForMessageAreaSearchMessagesResult = {
contacts: AddonMessagesMessageAreaContact[];
};
/**
* Result of WS core_message_message_search_users.
*/
export type AddonMessagesSearchUsersResult = {
contacts: AddonMessagesConversationMember[];
noncontacts: AddonMessagesConversationMember[];
};
/**
* Result of WS core_message_mark_message_read.
*/
export type AddonMessagesMarkMessageReadResult = {
messageid: number; // The id of the message in the messages table.
warnings?: CoreWSExternalWarning[];
};
/**
* Result of WS core_message_send_instant_messages.
*/
export type AddonMessagesSendInstantMessagesMessage = {
msgid: number; // Test this to know if it succeeds: id of the created message if it succeeded, -1 when failed.
clientmsgid?: string; // Your own id for the message.
errormessage?: string; // Error message - if it failed.
text?: string; // The text of the message.
timecreated?: number; // The timecreated timestamp for the message.
conversationid?: number; // The conversation id for this message.
useridfrom?: number; // The user id who sent the message.
candeletemessagesforallusers: boolean; // If the user can delete messages in the conversation for all users.
};
/**
* Result of WS core_message_send_messages_to_conversation.
*/
export type AddonMessagesSendMessagesToConversationMessage = {
id: number; // The id of the message.
useridfrom: number; // The id of the user who sent the message.
text: string; // The text of the message.
timecreated: number; // The timecreated timestamp for the message.
};
/**
* Calculated data for core_message_get_contacts.
*/
export type AddonMessagesGetContactsCalculatedData = {
blocked?: AddonMessagesBlockedUser[]; // Calculated in the app. List of blocked users.
};
/**
* Calculated data for core_message_get_conversation_messages.
*/
export type AddonMessagesGetConversationMessagesCalculatedData = {
canLoadMore?: boolean; // Calculated in the app. Whether more messages can be loaded.
};
/**
* Calculated data for message preferences.
*/
export type AddonMessagesMessagePreferencesCalculatedData = {
blocknoncontacts?: number; // Calculated in the app. Based on the result of core_message_get_user_message_preferences.
};
/**
* Calculated data for messages returned by core_message_get_messages.
*/
export type AddonMessagesGetMessagesMessageCalculatedData = {
pending?: boolean; // Calculated in the app. Whether the message is pending to be sent.
read?: number; // Calculated in the app. Whether the message has been read.
};
/**
* Calculated data for contact for message area.
*/
export type AddonMessagesMessageAreaContactCalculatedData = {
id?: number; // Calculated in the app. User ID.
};

View File

@ -18,7 +18,7 @@ import { CoreSitesProvider } from '@providers/sites';
import { CoreSyncBaseProvider } from '@classes/base-sync';
import { CoreAppProvider } from '@providers/app';
import { AddonMessagesOfflineProvider } from './messages-offline';
import { AddonMessagesProvider } from './messages';
import { AddonMessagesProvider, AddonMessagesConversationFormatted } from './messages';
import { CoreUserProvider } from '@core/user/providers/user';
import { CoreEventsProvider } from '@providers/events';
import { CoreTextUtilsProvider } from '@providers/utils/text';
@ -258,7 +258,7 @@ export class AddonMessagesSyncProvider extends CoreSyncBaseProvider {
// Get conversation name and add errors to warnings array.
return this.messagesProvider.getConversation(conversationId, false, false).catch(() => {
// Ignore errors.
return {};
return <AddonMessagesConversationFormatted> {};
}).then((conversation) => {
errors.forEach((error) => {
warnings.push(this.translate.instant('addon.messages.warningconversationmessagenotsent', {

View File

@ -21,7 +21,7 @@ import { CoreSitesProvider } from '@providers/sites';
import { CoreTextUtilsProvider } from '@providers/utils/text';
import { CoreUserProvider } from '@core/user/providers/user';
import { coreSlideInOut } from '@classes/animations';
import { AddonNotesProvider } from '../../providers/notes';
import { AddonNotesProvider, AddonNotesNoteFormatted } from '../../providers/notes';
import { AddonNotesOfflineProvider } from '../../providers/notes-offline';
import { AddonNotesSyncProvider } from '../../providers/notes-sync';
@ -44,7 +44,7 @@ export class AddonNotesListComponent implements OnInit, OnDestroy {
type = 'course';
refreshIcon = 'spinner';
syncIcon = 'spinner';
notes: any[];
notes: AddonNotesNoteFormatted[];
hasOffline = false;
notesLoaded = false;
user: any;
@ -101,21 +101,21 @@ export class AddonNotesListComponent implements OnInit, OnDestroy {
// Ignore errors.
}).then(() => {
return this.notesProvider.getNotes(this.courseId, this.userId).then((notes) => {
notes = notes[this.type + 'notes'] || [];
const notesList: AddonNotesNoteFormatted[] = notes[this.type + 'notes'] || [];
return this.notesProvider.setOfflineDeletedNotes(notes, this.courseId).then((notes) => {
return this.notesProvider.setOfflineDeletedNotes(notesList, this.courseId).then((notesList) => {
this.hasOffline = notes.some((note) => note.offline || note.deleted);
this.hasOffline = notesList.some((note) => note.offline || note.deleted);
if (this.userId) {
this.notes = notes;
this.notes = notesList;
// Get the user profile to retrieve the user image.
return this.userProvider.getProfile(this.userId, this.courseId, true).then((user) => {
this.user = user;
});
} else {
return this.notesProvider.getNotesUserData(notes, this.courseId).then((notes) => {
return this.notesProvider.getNotesUserData(notesList, this.courseId).then((notes) => {
this.notes = notes;
});
}
@ -126,7 +126,7 @@ export class AddonNotesListComponent implements OnInit, OnDestroy {
}).finally(() => {
let canDelete = this.notes && this.notes.length > 0;
if (canDelete && this.type == 'personal') {
canDelete = this.notes.find((note) => {
canDelete = !!this.notes.find((note) => {
return note.usermodified == this.currentUserId;
});
}
@ -178,6 +178,7 @@ export class AddonNotesListComponent implements OnInit, OnDestroy {
addNote(e: Event): void {
e.preventDefault();
e.stopPropagation();
const modal = this.modalCtrl.create('AddonNotesAddPage', { userId: this.userId, courseId: this.courseId, type: this.type });
modal.onDidDismiss((data) => {
if (data && data.sent && data.type) {
@ -192,6 +193,7 @@ export class AddonNotesListComponent implements OnInit, OnDestroy {
this.typeChanged();
}
});
modal.present();
}
@ -201,7 +203,7 @@ export class AddonNotesListComponent implements OnInit, OnDestroy {
* @param e Click event.
* @param note Note to delete.
*/
deleteNote(e: Event, note: any): void {
deleteNote(e: Event, note: AddonNotesNoteFormatted): void {
e.preventDefault();
e.stopPropagation();
@ -226,7 +228,7 @@ export class AddonNotesListComponent implements OnInit, OnDestroy {
* @param e Click event.
* @param note Note to delete.
*/
undoDeleteNote(e: Event, note: any): void {
undoDeleteNote(e: Event, note: AddonNotesNoteFormatted): void {
e.preventDefault();
e.stopPropagation();

View File

@ -22,6 +22,7 @@ import { TranslateService } from '@ngx-translate/core';
import { CoreUserProvider } from '@core/user/providers/user';
import { AddonNotesOfflineProvider } from './notes-offline';
import { CorePushNotificationsProvider } from '@core/pushnotifications/providers/pushnotifications';
import { CoreWSExternalWarning } from '@providers/ws';
/**
* Service to handle notes.
@ -119,9 +120,9 @@ export class AddonNotesProvider {
* @return Promise resolved when added, rejected otherwise. Promise resolved doesn't mean that notes
* have been added, the resolve param can contain errors for notes not sent.
*/
addNotesOnline(notes: any[], siteId?: string): Promise<any> {
addNotesOnline(notes: any[], siteId?: string): Promise<AddonNotesCreateNotesNote[]> {
if (!notes || !notes.length) {
return Promise.resolve();
return Promise.resolve([]);
}
return this.sitesProvider.getSite(siteId).then((site) => {
@ -142,7 +143,7 @@ export class AddonNotesProvider {
* @return Promise resolved when deleted, rejected otherwise. Promise resolved doesn't mean that notes
* have been deleted, the resolve param can contain errors for notes not deleted.
*/
deleteNote(note: any, courseId: number, siteId?: string): Promise<void> {
deleteNote(note: AddonNotesNoteFormatted, courseId: number, siteId?: string): Promise<void> {
siteId = siteId || this.sitesProvider.getCurrentSiteId();
if (note.offline) {
@ -190,7 +191,7 @@ export class AddonNotesProvider {
notes: noteIds
};
return site.write('core_notes_delete_notes', data).then((response) => {
return site.write('core_notes_delete_notes', data).then((response: CoreWSExternalWarning[]) => {
// A note was deleted, invalidate the course notes.
return this.invalidateNotes(courseId, undefined, siteId).catch(() => {
// Ignore errors.
@ -288,7 +289,9 @@ export class AddonNotesProvider {
* @param siteId Site ID. If not defined, current site.
* @return Promise to be resolved when the notes are retrieved.
*/
getNotes(courseId: number, userId?: number, ignoreCache?: boolean, onlyOnline?: boolean, siteId?: string): Promise<any> {
getNotes(courseId: number, userId?: number, ignoreCache?: boolean, onlyOnline?: boolean, siteId?: string)
: Promise<AddonNotesGetCourseNotesResult> {
this.logger.debug('Get notes for course ' + courseId);
return this.sitesProvider.getSite(siteId).then((site) => {
@ -310,7 +313,7 @@ export class AddonNotesProvider {
preSets.emergencyCache = false;
}
return site.read('core_notes_get_course_notes', data, preSets).then((notes) => {
return site.read('core_notes_get_course_notes', data, preSets).then((notes: AddonNotesGetCourseNotesResult) => {
if (onlyOnline) {
return notes;
}
@ -339,9 +342,11 @@ export class AddonNotesProvider {
* @param notes Array of notes.
* @param courseId ID of the course the notes belong to.
* @param siteId Site ID. If not defined, current site.
* @return [description]
* @return Promise resolved when done.
*/
setOfflineDeletedNotes(notes: any[], courseId: number, siteId?: string): Promise<any> {
setOfflineDeletedNotes(notes: AddonNotesNoteFormatted[], courseId: number, siteId?: string)
: Promise<AddonNotesNoteFormatted[]> {
return this.notesOffline.getCourseDeletedNotes(courseId, siteId).then((deletedNotes) => {
notes.forEach((note) => {
note.deleted = deletedNotes.some((n) => n.noteid == note.id);
@ -358,7 +363,7 @@ export class AddonNotesProvider {
* @param courseId ID of the course the notes belong to.
* @return Promise always resolved. Resolve param is the formatted notes.
*/
getNotesUserData(notes: any[], courseId: number): Promise<any> {
getNotesUserData(notes: AddonNotesNoteFormatted[], courseId: number): Promise<AddonNotesNoteFormatted[]> {
const promises = notes.map((note) => {
// Get the user profile to retrieve the user image.
return this.userProvider.getProfile(note.userid, note.courseid, true).then((user) => {
@ -400,7 +405,7 @@ export class AddonNotesProvider {
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved when the WS call is successful.
*/
logView(courseId: number, userId?: number, siteId?: string): Promise<any> {
logView(courseId: number, userId?: number, siteId?: string): Promise<AddonNotesViewNotesResult> {
return this.sitesProvider.getSite(siteId).then((site) => {
const params = {
courseid: courseId,
@ -413,3 +418,57 @@ export class AddonNotesProvider {
});
}
}
/**
* Note data returned by core_notes_get_course_notes.
*/
export type AddonNotesNote = {
id: number; // Id of this note.
courseid: number; // Id of the course.
userid: number; // User id.
content: string; // The content text formated.
format: number; // Content format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
created: number; // Time created (timestamp).
lastmodified: number; // Time of last modification (timestamp).
usermodified: number; // User id of the creator of this note.
publishstate: string; // State of the note (i.e. draft, public, site).
};
/**
* Result of WS core_notes_get_course_notes.
*/
export type AddonNotesGetCourseNotesResult = {
sitenotes?: AddonNotesNote[]; // Site notes.
coursenotes?: AddonNotesNote[]; // Couse notes.
personalnotes?: AddonNotesNote[]; // Personal notes.
canmanagesystemnotes?: boolean; // Whether the user can manage notes at system level.
canmanagecoursenotes?: boolean; // Whether the user can manage notes at the given course.
warnings?: CoreWSExternalWarning[];
};
/**
* Note returned by WS core_notes_create_notes.
*/
export type AddonNotesCreateNotesNote = {
clientnoteid?: string; // Your own id for the note.
noteid: number; // ID of the created note when successful, -1 when failed.
errormessage?: string; // Error message - if failed.
};
/**
* Result of WS core_notes_view_notes.
*/
export type AddonNotesViewNotesResult = {
status: boolean; // Status: true if success.
warnings?: CoreWSExternalWarning[];
};
/**
* Notes with some calculated data.
*/
export type AddonNotesNoteFormatted = AddonNotesNote & {
offline?: boolean; // Calculated in the app. Whether it's an offline note.
deleted?: boolean; // Calculated in the app. Whether the note was deleted in offline.
userfullname?: string; // Calculated in the app. Full name of the user the note refers to.
userprofileimageurl?: string; // Calculated in the app. Avatar url of the user the note refers to.
};

View File

@ -20,7 +20,7 @@ import { CoreTextUtilsProvider } from '@providers/utils/text';
import { CoreEventsProvider, CoreEventObserver } from '@providers/events';
import { CoreSitesProvider } from '@providers/sites';
import { CoreUtilsProvider } from '@providers/utils/utils';
import { AddonNotificationsProvider } from '../../providers/notifications';
import { AddonNotificationsProvider, AddonNotificationsAnyNotification } from '../../providers/notifications';
import { AddonNotificationsHelperProvider } from '../../providers/helper';
import { CorePushNotificationsDelegate } from '@core/pushnotifications/providers/delegate';
@ -34,7 +34,7 @@ import { CorePushNotificationsDelegate } from '@core/pushnotifications/providers
})
export class AddonNotificationsListPage {
notifications = [];
notifications: AddonNotificationsAnyNotification[] = [];
notificationsLoaded = false;
canLoadMore = false;
loadMoreError = false;
@ -130,11 +130,12 @@ export class AddonNotificationsListPage {
*
* @param notifications Array of notification objects.
*/
protected markNotificationsAsRead(notifications: any[]): void {
protected markNotificationsAsRead(notifications: AddonNotificationsAnyNotification[]): void {
let promise;
if (notifications.length > 0) {
const promises = notifications.map((notification) => {
const promises: Promise<any>[] = notifications.map((notification) => {
if (notification.read) {
// Already read, don't mark it.
return Promise.resolve();
@ -202,7 +203,7 @@ export class AddonNotificationsListPage {
*
* @param notification The notification object.
*/
protected formatText(notification: any): void {
protected formatText(notification: AddonNotificationsAnyNotification): void {
const text = notification.mobiletext.replace(/-{4,}/ig, '');
notification.mobiletext = this.textUtils.replaceNewLines(text, '<br>');
}

View File

@ -14,7 +14,11 @@
import { Component, OnDestroy, Optional } from '@angular/core';
import { IonicPage, NavController } from 'ionic-angular';
import { AddonNotificationsProvider } from '../../providers/notifications';
import {
AddonNotificationsProvider, AddonNotificationsNotificationPreferences, AddonNotificationsNotificationPreferencesProcessor,
AddonNotificationsNotificationPreferencesComponent, AddonNotificationsNotificationPreferencesNotification,
AddonNotificationsNotificationPreferencesNotificationProcessorState
} from '../../providers/notifications';
import { CoreUserProvider } from '@core/user/providers/user';
import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { CoreSettingsHelper } from '@core/settings/providers/helper';
@ -38,10 +42,10 @@ import { CoreSplitViewComponent } from '@components/split-view/split-view';
export class AddonNotificationsSettingsPage implements OnDestroy {
protected updateTimeout: any;
components: any[];
preferences: any;
components: AddonNotificationsNotificationPreferencesComponent[];
preferences: AddonNotificationsNotificationPreferences;
preferencesLoaded: boolean;
currentProcessor: any;
currentProcessor: AddonNotificationsNotificationPreferencesProcessorFormatted;
notifPrefsEnabled: boolean;
canChangeSound: boolean;
notificationSound: boolean;
@ -99,7 +103,7 @@ export class AddonNotificationsSettingsPage implements OnDestroy {
// Get display data of message output handlers (thery are displayed in the context menu),
this.processorHandlers = [];
if (preferences.processors) {
preferences.processors.forEach((processor) => {
preferences.processors.forEach((processor: AddonNotificationsNotificationPreferencesProcessorFormatted) => {
processor.supported = this.messageOutputDelegate.hasHandler(processor.name, true);
if (processor.hassettings && processor.supported) {
this.processorHandlers.push(this.messageOutputDelegate.getDisplayData(processor));
@ -118,7 +122,7 @@ export class AddonNotificationsSettingsPage implements OnDestroy {
*
* @param processor Processor object.
*/
protected loadProcessor(processor: any): void {
protected loadProcessor(processor: AddonNotificationsNotificationPreferencesProcessorFormatted): void {
if (!processor) {
return;
}
@ -191,8 +195,9 @@ export class AddonNotificationsSettingsPage implements OnDestroy {
* @param notification Notification object.
* @param state State name, ['loggedin', 'loggedoff'].
*/
changePreference(notification: any, state: string): void {
const processorState = notification.currentProcessor[state];
changePreference(notification: AddonNotificationsNotificationPreferencesNotificationFormatted, state: string): void {
const processorState: AddonNotificationsNotificationPreferencesNotificationProcessorStateFormatted =
notification.currentProcessor[state];
const preferenceName = notification.preferencekey + '_' + processorState.name;
let value;
@ -211,6 +216,7 @@ export class AddonNotificationsSettingsPage implements OnDestroy {
}
processorState.updating = true;
this.userProvider.updateUserPreference(preferenceName, value).then(() => {
// Update the preferences since they were modified.
this.updatePreferencesAfterDelay();
@ -264,3 +270,25 @@ export class AddonNotificationsSettingsPage implements OnDestroy {
}
}
}
/**
* Notification preferences notification with some calculated data.
*/
type AddonNotificationsNotificationPreferencesNotificationFormatted = AddonNotificationsNotificationPreferencesNotification & {
currentProcessor?: AddonNotificationsNotificationPreferencesProcessorFormatted; // Calculated in the app. Current processor.
};
/**
* Notification preferences processor with some calculated data.
*/
type AddonNotificationsNotificationPreferencesProcessorFormatted = AddonNotificationsNotificationPreferencesProcessor & {
supported?: boolean; // Calculated in the app. Whether the processor is supported in the app.
};
/**
* State in notification processor in notification preferences component with some calculated data.
*/
type AddonNotificationsNotificationPreferencesNotificationProcessorStateFormatted =
AddonNotificationsNotificationPreferencesNotificationProcessorState & {
updating?: boolean; // Calculated in the app. Whether the state is being updated.
};

View File

@ -14,7 +14,9 @@
import { Injectable } from '@angular/core';
import { CoreSitesProvider } from '@providers/sites';
import { AddonNotificationsProvider } from './notifications';
import {
AddonNotificationsProvider, AddonNotificationsAnyNotification, AddonNotificationsGetMessagesMessage
} from './notifications';
/**
* Service that provides some helper functions for notifications.
@ -37,7 +39,7 @@ export class AddonNotificationsHelperProvider {
* @return Promise resolved with notifications and if can load more.
*/
getNotifications(notifications: any[], limit?: number, toDisplay: boolean = true, forceCache?: boolean, ignoreCache?: boolean,
siteId?: string): Promise<{notifications: any[], canLoadMore: boolean}> {
siteId?: string): Promise<{notifications: AddonNotificationsAnyNotification[], canLoadMore: boolean}> {
notifications = notifications || [];
limit = limit || AddonNotificationsProvider.LIST_LIMIT;
@ -80,7 +82,7 @@ export class AddonNotificationsHelperProvider {
promise = Promise.resolve(unread);
}
return promise.then((notifications) => {
return promise.then((notifications: AddonNotificationsGetMessagesMessage[]) => {
return {
notifications: notifications,
canLoadMore: notifications.length >= limit

View File

@ -20,8 +20,9 @@ import { CoreTextUtilsProvider } from '@providers/utils/text';
import { CoreTimeUtilsProvider } from '@providers/utils/time';
import { CoreUserProvider } from '@core/user/providers/user';
import { CoreEmulatorHelperProvider } from '@core/emulator/providers/helper';
import { AddonMessagesProvider } from '@addon/messages/providers/messages';
import { AddonMessagesProvider, AddonMessagesMarkMessageReadResult } from '@addon/messages/providers/messages';
import { CoreSite } from '@classes/site';
import { CoreWSExternalWarning } from '@providers/ws';
/**
* Service to handle notifications.
@ -51,14 +52,13 @@ export class AddonNotificationsProvider {
* @param read Whether the notifications are read or unread.
* @return Promise resolved with notifications.
*/
protected formatNotificationsData(notifications: any[], read?: boolean): Promise<any> {
protected formatNotificationsData(notifications: AddonNotificationsAnyNotification[], read?: boolean): Promise<any> {
const promises = notifications.map((notification) => {
// Set message to show.
if (notification.component && notification.component == 'mod_forum') {
notification.mobiletext = notification.smallmessage;
} else if (notification.component && notification.component == 'moodle' && notification.name == 'insights') {
notification.mobiletext = notification.fullmessagehtml;
} else {
notification.mobiletext = notification.fullmessage;
}
@ -117,7 +117,7 @@ export class AddonNotificationsProvider {
* @param siteId Site ID. If not defined, use current site.
* @return Promise resolved with the notification preferences.
*/
getNotificationPreferences(siteId?: string): Promise<any> {
getNotificationPreferences(siteId?: string): Promise<AddonNotificationsNotificationPreferences> {
this.logger.debug('Get notification preferences');
return this.sitesProvider.getSite(siteId).then((site) => {
@ -126,7 +126,9 @@ export class AddonNotificationsProvider {
updateFrequency: CoreSite.FREQUENCY_SOMETIMES
};
return site.read('core_message_get_user_notification_preferences', {}, preSets).then((data) => {
return site.read('core_message_get_user_notification_preferences', {}, preSets)
.then((data: AddonNotificationsGetUserNotificationPreferencesResult) => {
return data.preferences;
});
});
@ -154,7 +156,7 @@ export class AddonNotificationsProvider {
* @return Promise resolved with notifications.
*/
getNotifications(read: boolean, limitFrom: number, limitNumber: number = 0, toDisplay: boolean = true,
forceCache?: boolean, ignoreCache?: boolean, siteId?: string): Promise<any[]> {
forceCache?: boolean, ignoreCache?: boolean, siteId?: string): Promise<AddonNotificationsGetMessagesMessage[]> {
limitNumber = limitNumber || AddonNotificationsProvider.LIST_LIMIT;
this.logger.debug('Get ' + (read ? 'read' : 'unread') + ' notifications from ' + limitFrom + '. Limit: ' + limitNumber);
@ -176,7 +178,7 @@ export class AddonNotificationsProvider {
};
// Get unread notifications.
return site.read('core_message_get_messages', data, preSets).then((response) => {
return site.read('core_message_get_messages', data, preSets).then((response: AddonNotificationsGetMessagesResult) => {
if (response.messages) {
const notifications = response.messages;
@ -209,7 +211,7 @@ export class AddonNotificationsProvider {
* @since 3.2
*/
getPopupNotifications(offset: number, limit?: number, toDisplay: boolean = true, forceCache?: boolean, ignoreCache?: boolean,
siteId?: string): Promise<{notifications: any[], canLoadMore: boolean}> {
siteId?: string): Promise<{notifications: AddonNotificationsPopupNotificationFormatted[], canLoadMore: boolean}> {
limit = limit || AddonNotificationsProvider.LIST_LIMIT;
@ -230,17 +232,17 @@ export class AddonNotificationsProvider {
};
// Get notifications.
return site.read('message_popup_get_popup_notifications', data, preSets).then((response) => {
return site.read('message_popup_get_popup_notifications', data, preSets)
.then((response: AddonNotificationsGetPopupNotificationsResult) => {
if (response.notifications) {
const result: any = {
canLoadMore: response.notifications.length > limit
},
notifications = response.notifications.slice(0, limit);
const result = {
canLoadMore: response.notifications.length > limit,
notifications: response.notifications.slice(0, limit)
};
result.notifications = notifications;
return this.formatNotificationsData(notifications).then(() => {
const first = notifications[0];
return this.formatNotificationsData(result.notifications).then(() => {
const first = result.notifications[0];
if (this.appProvider.isDesktop() && toDisplay && offset === 0 && first && !first.read) {
// Store the last received notification. Don't block the user for this.
@ -269,7 +271,7 @@ export class AddonNotificationsProvider {
* @return Promise resolved with notifications.
*/
getReadNotifications(limitFrom: number, limitNumber: number, toDisplay: boolean = true,
forceCache?: boolean, ignoreCache?: boolean, siteId?: string): Promise<any[]> {
forceCache?: boolean, ignoreCache?: boolean, siteId?: string): Promise<AddonNotificationsGetMessagesMessage[]> {
return this.getNotifications(true, limitFrom, limitNumber, toDisplay, forceCache, ignoreCache, siteId);
}
@ -285,7 +287,7 @@ export class AddonNotificationsProvider {
* @return Promise resolved with notifications.
*/
getUnreadNotifications(limitFrom: number, limitNumber: number, toDisplay: boolean = true,
forceCache?: boolean, ignoreCache?: boolean, siteId?: string): Promise<any[]> {
forceCache?: boolean, ignoreCache?: boolean, siteId?: string): Promise<AddonNotificationsGetMessagesMessage[]> {
return this.getNotifications(false, limitFrom, limitNumber, toDisplay, forceCache, ignoreCache, siteId);
}
@ -349,7 +351,7 @@ export class AddonNotificationsProvider {
* @return Resolved when done.
* @since 3.2
*/
markAllNotificationsAsRead(): Promise<any> {
markAllNotificationsAsRead(): Promise<boolean> {
const params = {
useridto: this.sitesProvider.getCurrentSiteUserId()
};
@ -362,10 +364,12 @@ export class AddonNotificationsProvider {
*
* @param notificationId ID of notification to mark as read
* @param siteId Site ID. If not defined, current site.
* @return Resolved when done.
* @return Promise resolved when done.
* @since 3.5
*/
markNotificationRead(notificationId: number, siteId?: string): Promise<any> {
markNotificationRead(notificationId: number, siteId?: string)
: Promise<AddonNotificationsMarkNotificationReadResult | AddonMessagesMarkMessageReadResult> {
return this.sitesProvider.getSite(siteId).then((site) => {
if (site.wsAvailable('core_message_mark_notification_read')) {
@ -436,3 +440,179 @@ export class AddonNotificationsProvider {
return this.sitesProvider.wsAvailableInCurrentSite('core_message_get_user_notification_preferences');
}
}
/**
* Preferences returned by core_message_get_user_notification_preferences.
*/
export type AddonNotificationsNotificationPreferences = {
userid: number; // User id.
disableall: number | boolean; // Whether all the preferences are disabled.
processors: AddonNotificationsNotificationPreferencesProcessor[]; // Config form values.
components: AddonNotificationsNotificationPreferencesComponent[]; // Available components.
};
/**
* Processor in notification preferences.
*/
export type AddonNotificationsNotificationPreferencesProcessor = {
displayname: string; // Display name.
name: string; // Processor name.
hassettings: boolean; // Whether has settings.
contextid: number; // Context id.
userconfigured: number; // Whether is configured by the user.
};
/**
* Component in notification preferences.
*/
export type AddonNotificationsNotificationPreferencesComponent = {
displayname: string; // Display name.
notifications: AddonNotificationsNotificationPreferencesNotification[]; // List of notificaitons for the component.
};
/**
* Notification processor in notification preferences component.
*/
export type AddonNotificationsNotificationPreferencesNotification = {
displayname: string; // Display name.
preferencekey: string; // Preference key.
processors: AddonNotificationsNotificationPreferencesNotificationProcessor[]; // Processors values for this notification.
};
/**
* Notification processor in notification preferences component.
*/
export type AddonNotificationsNotificationPreferencesNotificationProcessor = {
displayname: string; // Display name.
name: string; // Processor name.
locked: boolean; // Is locked by admin?.
lockedmessage?: string; // Text to display if locked.
userconfigured: number; // Is configured?.
loggedin: AddonNotificationsNotificationPreferencesNotificationProcessorState;
loggedoff: AddonNotificationsNotificationPreferencesNotificationProcessorState;
};
/**
* State in notification processor in notification preferences component.
*/
export type AddonNotificationsNotificationPreferencesNotificationProcessorState = {
name: string; // Name.
displayname: string; // Display name.
checked: boolean; // Is checked?.
};
/**
* Result of WS core_message_get_messages.
*/
export type AddonNotificationsGetMessagesResult = {
messages: AddonNotificationsGetMessagesMessage[];
warnings?: CoreWSExternalWarning[];
};
/**
* Message data returned by core_message_get_messages.
*/
export type AddonNotificationsGetMessagesMessage = {
id: number; // Message id.
useridfrom: number; // User from id.
useridto: number; // User to id.
subject: string; // The message subject.
text: string; // The message text formated.
fullmessage: string; // The message.
fullmessageformat: number; // Fullmessage format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
fullmessagehtml: string; // The message in html.
smallmessage: string; // The shorten message.
notification: number; // Is a notification?.
contexturl: string; // Context URL.
contexturlname: string; // Context URL link name.
timecreated: number; // Time created.
timeread: number; // Time read.
usertofullname: string; // User to full name.
userfromfullname: string; // User from full name.
component?: string; // @since 3.7. The component that generated the notification.
eventtype?: string; // @since 3.7. The type of notification.
customdata?: any; // @since 3.7. Custom data to be passed to the message processor.
};
/**
* Message data returned by core_message_get_messages with some calculated data.
*/
export type AddonNotificationsGetMessagesMessageFormatted =
AddonNotificationsGetMessagesMessage & AddonNotificationsNotificationCalculatedData;
/**
* Result of WS message_popup_get_popup_notifications.
*/
export type AddonNotificationsGetPopupNotificationsResult = {
notifications: AddonNotificationsPopupNotification[];
unreadcount: number; // The number of unread message for the given user.
};
/**
* Notification returned by message_popup_get_popup_notifications.
*/
export type AddonNotificationsPopupNotification = {
id: number; // Notification id (this is not guaranteed to be unique within this result set).
useridfrom: number; // User from id.
useridto: number; // User to id.
subject: string; // The notification subject.
shortenedsubject: string; // The notification subject shortened with ellipsis.
text: string; // The message text formated.
fullmessage: string; // The message.
fullmessageformat: number; // Fullmessage format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
fullmessagehtml: string; // The message in html.
smallmessage: string; // The shorten message.
contexturl: string; // Context URL.
contexturlname: string; // Context URL link name.
timecreated: number; // Time created.
timecreatedpretty: string; // Time created in a pretty format.
timeread: number; // Time read.
read: boolean; // Notification read status.
deleted: boolean; // Notification deletion status.
iconurl: string; // URL for notification icon.
component?: string; // The component that generated the notification.
eventtype?: string; // The type of notification.
customdata?: any; // @since 3.7. Custom data to be passed to the message processor.
};
/**
* Notification returned by message_popup_get_popup_notifications.
*/
export type AddonNotificationsPopupNotificationFormatted =
AddonNotificationsPopupNotification & AddonNotificationsNotificationCalculatedData;
/**
* Any kind of notification that can be retrieved.
*/
export type AddonNotificationsAnyNotification =
AddonNotificationsPopupNotificationFormatted | AddonNotificationsGetMessagesMessageFormatted;
/**
* Result of WS core_message_get_user_notification_preferences.
*/
export type AddonNotificationsGetUserNotificationPreferencesResult = {
preferences: AddonNotificationsNotificationPreferences;
warnings?: CoreWSExternalWarning[];
};
/**
* Result of WS core_message_mark_notification_read.
*/
export type AddonNotificationsMarkNotificationReadResult = {
notificationid: number; // Id of the notification.
warnings?: CoreWSExternalWarning[];
};
/**
* Calculated data for messages returned by core_message_get_messages.
*/
export type AddonNotificationsNotificationCalculatedData = {
mobiletext?: string; // Calculated in the app. Text to display for the notification.
moodlecomponent?: string; // Calculated in the app. Moodle's component.
notif?: number; // Calculated in the app. Whether it's a notification.
notification?: number; // Calculated in the app in some cases. Whether it's a notification.
read?: boolean; // Calculated in the app. Whether the notifications is read.
courseid?: number; // Calculated in the app. Course the notification belongs to.
profileimageurlfrom?: string; // Calculated in the app. Avatar of user that sent the notification.
userfromfullname?: string; // Calculated in the app in some cases. User from full name.
};

View File

@ -407,3 +407,27 @@ export class CoreCommentsProvider {
});
}
}
/**
* Data returned by comment_area_exporter.
*/
export type CoreCommentsArea = {
component: string; // Component.
commentarea: string; // Commentarea.
itemid: number; // Itemid.
courseid: number; // Courseid.
contextid: number; // Contextid.
cid: string; // Cid.
autostart: boolean; // Autostart.
canpost: boolean; // Canpost.
canview: boolean; // Canview.
count: number; // Count.
collapsediconkey: string; // Collapsediconkey.
displaytotalcount: boolean; // Displaytotalcount.
displaycancel: boolean; // Displaycancel.
fullwidth: boolean; // Fullwidth.
linktext: string; // Linktext.
notoggle: boolean; // Notoggle.
template: string; // Template.
canpostorhascomments: boolean; // Canpostorhascomments.
};

View File

@ -1135,3 +1135,38 @@ export class CoreCourseProvider {
}, siteId);
}
}
/**
* Data returned by course_summary_exporter.
*/
export type CoreCourseSummary = {
id: number; // Id.
fullname: string; // Fullname.
shortname: string; // Shortname.
idnumber: string; // Idnumber.
summary: string; // Summary.
summaryformat: number; // Summary format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
startdate: number; // Startdate.
enddate: number; // Enddate.
visible: boolean; // Visible.
fullnamedisplay: string; // Fullnamedisplay.
viewurl: string; // Viewurl.
courseimage: string; // Courseimage.
progress?: number; // Progress.
hasprogress: boolean; // Hasprogress.
isfavourite: boolean; // Isfavourite.
hidden: boolean; // Hidden.
timeaccess?: number; // Timeaccess.
showshortname: boolean; // Showshortname.
coursecategory: string; // Coursecategory.
};
/**
* Data returned by course_module_summary_exporter.
*/
export type CoreCourseModuleSummary = {
id: number; // Id.
name: string; // Name.
url?: string; // Url.
iconurl: string; // Iconurl.
};

View File

@ -17,74 +17,6 @@ import { TranslateService } from '@ngx-translate/core';
import { CoreSitesProvider } from '@providers/sites';
import { CoreSite, CoreSiteWSPreSets } from '@classes/site';
/**
* Structure of a tag cloud returned by WS.
*/
export interface CoreTagCloud {
tags: CoreTagCloudTag[];
tagscount: number;
totalcount: number;
}
/**
* Structure of a tag cloud tag returned by WS.
*/
export interface CoreTagCloudTag {
name: string;
viewurl: string;
flag: boolean;
isstandard: boolean;
count: number;
size: number;
}
/**
* Structure of a tag collection returned by WS.
*/
export interface CoreTagCollection {
id: number;
name: string;
isdefault: boolean;
component: string;
sortoder: number;
searchable: boolean;
customurl: string;
}
/**
* Structure of a tag index returned by WS.
*/
export interface CoreTagIndex {
tagid: number;
ta: number;
component: string;
itemtype: string;
nextpageurl: string;
prevpageurl: string;
exclusiveurl: string;
exclusivetext: string;
title: string;
content: string;
hascontent: number;
anchor: string;
}
/**
* Structure of a tag item returned by WS.
*/
export interface CoreTagItem {
id: number;
name: string;
rawname: string;
isstandard: boolean;
tagcollid: number;
taginstanceid: number;
taginstancecontextid: number;
itemid: number;
ordering: number;
flag: number;
}
/**
* Service to handle tags.
*/
@ -343,3 +275,71 @@ export class CoreTagProvider {
+ contextId + ':' + (recursive ? 1 : 0);
}
}
/**
* Structure of a tag cloud returned by WS.
*/
export type CoreTagCloud = {
tags: CoreTagCloudTag[];
tagscount: number;
totalcount: number;
};
/**
* Structure of a tag cloud tag returned by WS.
*/
export type CoreTagCloudTag = {
name: string;
viewurl: string;
flag: boolean;
isstandard: boolean;
count: number;
size: number;
};
/**
* Structure of a tag collection returned by WS.
*/
export type CoreTagCollection = {
id: number;
name: string;
isdefault: boolean;
component: string;
sortoder: number;
searchable: boolean;
customurl: string;
};
/**
* Structure of a tag index returned by WS.
*/
export type CoreTagIndex = {
tagid: number;
ta: number;
component: string;
itemtype: string;
nextpageurl: string;
prevpageurl: string;
exclusiveurl: string;
exclusivetext: string;
title: string;
content: string;
hascontent: number;
anchor: string;
};
/**
* Structure of a tag item returned by WS.
*/
export type CoreTagItem = {
id: number;
name: string;
rawname: string;
isstandard: boolean;
tagcollid: number;
taginstanceid: number;
taginstancecontextid: number;
itemid: number;
ordering: number;
flag: number;
};

View File

@ -634,3 +634,21 @@ export class CoreUserProvider {
});
}
}
/**
* Data returned by user_summary_exporter.
*/
export type CoreUserSummary = {
id: number; // Id.
email: string; // Email.
idnumber: string; // Idnumber.
phone1: string; // Phone1.
phone2: string; // Phone2.
department: string; // Department.
institution: string; // Institution.
fullname: string; // Fullname.
identity: string; // Identity.
profileurl: string; // Profileurl.
profileimageurl: string; // Profileimageurl.
profileimageurlsmall: string; // Profileimageurlsmall.
};

View File

@ -81,126 +81,6 @@ export interface CoreWSAjaxPreSets {
useGet?: boolean;
}
/**
* Error returned by a WS call.
*/
export interface CoreWSError {
/**
* The error message.
*/
message: string;
/**
* Name of the exception. Undefined for local errors (fake WS errors).
*/
exception?: string;
/**
* The error code. Undefined for local errors (fake WS errors).
*/
errorcode?: string;
}
/**
* File upload options.
*/
export interface CoreWSFileUploadOptions extends FileUploadOptions {
/**
* The file area where to put the file. By default, 'draft'.
*/
fileArea?: string;
/**
* Item ID of the area where to put the file. By default, 0.
*/
itemId?: number;
}
/**
* Structure of warnings returned by WS.
*/
export type CoreWSExternalWarning = {
/**
* Item.
* @type {string}
*/
item?: string;
/**
* Item id.
* @type {number}
*/
itemid?: number;
/**
* The warning code can be used by the client app to implement specific behaviour.
* @type {string}
*/
warningcode: string;
/**
* Untranslated english message to explain the warning.
* @type {string}
*/
message: string;
};
/**
* Structure of files returned by WS.
*/
export type CoreWSExternalFile = {
/**
* File name.
* @type {string}
*/
filename?: string;
/**
* File path.
* @type {string}
*/
filepath?: string;
/**
* File size.
* @type {number}
*/
filesize?: number;
/**
* Downloadable file url.
* @type {string}
*/
fileurl?: string;
/**
* Time modified.
* @type {number}
*/
timemodified?: number;
/**
* File mime type.
* @type {string}
*/
mimetype?: string;
/**
* Whether is an external file.
* @type {number}
*/
isexternalfile?: number;
/**
* The repository type for external files.
* @type {string}
*/
repositorytype?: string;
};
/**
* This service allows performing WS calls and download/upload files.
*/
@ -948,3 +828,127 @@ export class CoreWSProvider {
});
}
}
/**
* Error returned by a WS call.
*/
export interface CoreWSError {
/**
* The error message.
*/
message: string;
/**
* Name of the exception. Undefined for local errors (fake WS errors).
*/
exception?: string;
/**
* The error code. Undefined for local errors (fake WS errors).
*/
errorcode?: string;
}
/**
* File upload options.
*/
export interface CoreWSFileUploadOptions extends FileUploadOptions {
/**
* The file area where to put the file. By default, 'draft'.
*/
fileArea?: string;
/**
* Item ID of the area where to put the file. By default, 0.
*/
itemId?: number;
}
/**
* Structure of warnings returned by WS.
*/
export type CoreWSExternalWarning = {
/**
* Item.
*/
item?: string;
/**
* Item id.
*/
itemid?: number;
/**
* The warning code can be used by the client app to implement specific behaviour.
*/
warningcode: string;
/**
* Untranslated english message to explain the warning.
*/
message: string;
};
/**
* Structure of files returned by WS.
*/
export type CoreWSExternalFile = {
/**
* File name.
*/
filename?: string;
/**
* File path.
*/
filepath?: string;
/**
* File size.
*/
filesize?: number;
/**
* Downloadable file url.
*/
fileurl?: string;
/**
* Time modified.
*/
timemodified?: number;
/**
* File mime type.
*/
mimetype?: string;
/**
* Whether is an external file.
*/
isexternalfile?: number;
/**
* The repository type for external files.
*/
repositorytype?: string;
};
/**
* Data returned by date_exporter.
*/
export type CoreWSDate = {
seconds: number; // Seconds.
minutes: number; // Minutes.
hours: number; // Hours.
mday: number; // Mday.
wday: number; // Wday.
mon: number; // Mon.
year: number; // Year.
yday: number; // Yday.
weekday: string; // Weekday.
month: string; // Month.
timestamp: number; // Timestamp.
};