Merge pull request #2097 from dpalou/MOBILE-3109

Mobile 3109
main
Juan Leyva 2019-09-25 16:29:26 +02:00 committed by GitHub
commit 391782253f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
121 changed files with 4672 additions and 980 deletions

View File

@ -21,16 +21,19 @@ function print_success {
function print_error {
tput setaf 1; echo " ERROR: $1"
tput setaf 0
}
function print_ok {
tput setaf 2; echo " OK: $1"
echo
tput setaf 0
}
function print_message {
tput setaf 3; echo "-------- $1"
echo
tput setaf 0
}
function print_title {
@ -38,4 +41,5 @@ function print_title {
echo
tput setaf 5; echo "$stepnumber $1"
tput setaf 5; echo '=================='
tput setaf 0
}

View File

@ -0,0 +1,99 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Script for converting a PHP WS structure to a TS type.
*
* The first parameter (required) is the path to the Moodle installation to use.
* The second parameter (required) is the name to the WS to convert.
* The third parameter (optional) is a number: 1 to convert the params structure,
* 0 to convert the returns structure. Defaults to 0.
*/
if (!isset($argv[1])) {
echo "ERROR: Please pass the path to the folder containing the Moodle installations as the first parameter.\n";
die();
}
if (!isset($argv[2])) {
echo "ERROR: Please pass the WS name as the second parameter.\n";
die();
}
define('CLI_SCRIPT', true);
require_once('ws_to_ts_functions.php');
$versions = array('master', '37', '36', '35', '34', '33', '32', '31');
$moodlespath = $argv[1];
$wsname = $argv[2];
$useparams = !!(isset($argv[3]) && $argv[3]);
$pathseparator = '/';
// Get the path to the script.
$index = strrpos(__FILE__, $pathseparator);
if ($index === false) {
$pathseparator = '\\';
$index = strrpos(__FILE__, $pathseparator);
}
$scriptfolder = substr(__FILE__, 0, $index);
$scriptpath = concatenate_paths($scriptfolder, 'get_ws_structure.php', $pathseparator);
$previousstructure = null;
$previousversion = null;
$libsloaded = false;
foreach ($versions as $version) {
$moodlepath = concatenate_paths($moodlespath, 'stable_' . $version, $pathseparator);
if (!$libsloaded) {
$libsloaded = true;
require($moodlepath . '/config.php');
require($CFG->dirroot . '/webservice/lib.php');
}
// Get the structure in this Moodle version.
$structure = shell_exec("php $scriptpath $moodlepath $wsname " . ($useparams ? 'true' : ''));
if (strpos($structure, 'ERROR:') === 0) {
echo "WS not found in version $version. Stop.\n";
break;
}
$structure = unserialize($structure);
if ($previousstructure != null) {
echo "*** Check changes from version $version to $previousversion ***\n";
$messages = detect_ws_changes($previousstructure, $structure);
if (count($messages) > 0) {
$haschanged = true;
foreach($messages as $message) {
echo "$message\n";
}
} else {
echo "No changes found.\n";
}
echo "\n";
}
$previousstructure = $structure;
$previousversion = $version;
}

View File

@ -0,0 +1,55 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Script for getting the PHP structure of a WS returns or params.
*
* The first parameter (required) is the path to the Moodle installation to use.
* The second parameter (required) is the name to the WS to convert.
* The third parameter (optional) is a number: 1 to convert the params structure,
* 0 to convert the returns structure. Defaults to 0.
*/
if (!isset($argv[1])) {
echo "ERROR: Please pass the Moodle path as the first parameter.\n";
die();
}
if (!isset($argv[2])) {
echo "ERROR: Please pass the WS name as the second parameter.\n";
die();
}
$moodlepath = $argv[1];
$wsname = $argv[2];
$useparams = !!(isset($argv[3]) && $argv[3]);
define('CLI_SCRIPT', true);
require($moodlepath . '/config.php');
require($CFG->dirroot . '/webservice/lib.php');
require_once('ws_to_ts_functions.php');
$structure = get_ws_structure($wsname, $useparams);
if ($structure === false) {
echo "ERROR: The WS wasn't found in this Moodle installation.\n";
die();
}
remove_default_closures($structure);
echo serialize($structure);

View File

@ -0,0 +1,62 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Script for converting a PHP WS structure to a TS type.
*
* The first parameter (required) is the path to the Moodle installation to use.
* The second parameter (required) is the name to the WS to convert.
* The third parameter (optional) is the name to put to the TS type. Defaults to "TypeName".
* The fourth parameter (optional) is a number: 1 to convert the params structure,
* 0 to convert the returns structure. Defaults to 0.
*/
if (!isset($argv[1])) {
echo "ERROR: Please pass the Moodle path as the first parameter.\n";
die();
}
if (!isset($argv[2])) {
echo "ERROR: Please pass the WS name as the second parameter.\n";
die();
}
$moodlepath = $argv[1];
$wsname = $argv[2];
$typename = isset($argv[3]) ? $argv[3] : 'TypeName';
$useparams = !!(isset($argv[4]) && $argv[4]);
define('CLI_SCRIPT', true);
require($moodlepath . '/config.php');
require($CFG->dirroot . '/webservice/lib.php');
require_once('ws_to_ts_functions.php');
$structure = get_ws_structure($wsname, $useparams);
if ($structure === false) {
echo "ERROR: The WS wasn't found in this Moodle installation.\n";
die();
}
if ($useparams) {
$description = "Params of WS $wsname.";
} else {
$description = "Result of WS $wsname.";
}
echo get_ts_doc(null, $description, '') . "export type $typename = " . convert_to_ts(null, $structure, $useparams) . ";\n";

View File

@ -0,0 +1,244 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Helper functions for converting a Moodle WS structure to a TS type.
*/
/**
* Get the structure of a WS params or returns.
*/
function get_ws_structure($wsname, $useparams) {
global $DB;
// get all the function descriptions
$functions = $DB->get_records('external_functions', array(), 'name');
$functiondescs = array();
foreach ($functions as $function) {
$functiondescs[$function->name] = external_api::external_function_info($function);
}
if (!isset($functiondescs[$wsname])) {
return false;
} else if ($useparams) {
return $functiondescs[$wsname]->parameters_desc;
} else {
return $functiondescs[$wsname]->returns_desc;
}
}
/**
* Fix a comment: make sure first letter is uppercase and add a dot at the end if needed.
*/
function fix_comment($desc) {
$desc = trim($desc);
$desc = ucfirst($desc);
if (substr($desc, -1) !== '.') {
$desc .= '.';
}
return $desc;
}
/**
* Get an inline comment based on a certain text.
*/
function get_inline_comment($desc) {
if (empty($desc)) {
return '';
}
return ' // ' . fix_comment($desc);
}
/**
* Add the TS documentation of a certain element.
*/
function get_ts_doc($type, $desc, $indentation) {
if (empty($desc)) {
// If no key, it's probably in an array. We only document object properties.
return '';
}
return $indentation . "/**\n" .
$indentation . " * " . fix_comment($desc) . "\n" .
(!empty($type) ? ($indentation . " * @type {" . $type . "}\n") : '') .
$indentation . " */\n";
}
/**
* Specify a certain type, with or without a key.
*/
function convert_key_type($key, $type, $required, $indentation) {
if ($key) {
// It has a key, it's inside an object.
return $indentation . "$key" . ($required == VALUE_OPTIONAL ? '?' : '') . ": $type";
} else {
// No key, it's probably in an array. Just include the type.
return $type;
}
}
/**
* Convert a certain element into a TS structure.
*/
function convert_to_ts($key, $value, $boolisnumber = false, $indentation = '', $arraydesc = '') {
if ($value instanceof external_value || $value instanceof external_warnings || $value instanceof external_files) {
// It's a basic field or a pre-defined type like warnings.
$type = 'string';
if ($value instanceof external_warnings) {
$type = 'CoreWSExternalWarning[]';
} else if ($value instanceof external_files) {
$type = 'CoreWSExternalFile[]';
} else if ($value->type == PARAM_BOOL && !$boolisnumber) {
$type = 'boolean';
} else if (($value->type == PARAM_BOOL && $boolisnumber) || $value->type == PARAM_INT || $value->type == PARAM_FLOAT ||
$value->type == PARAM_LOCALISEDFLOAT || $value->type == PARAM_PERMISSION || $value->type == PARAM_INTEGER ||
$value->type == PARAM_NUMBER) {
$type = 'number';
}
$result = convert_key_type($key, $type, $value->required, $indentation);
return $result;
} else if ($value instanceof external_single_structure) {
// It's an object.
$result = convert_key_type($key, '{', $value->required, $indentation);
if ($arraydesc) {
// It's an array of objects. Print the array description now.
$result .= get_inline_comment($arraydesc);
}
$result .= "\n";
foreach ($value->keys as $key => $value) {
$result .= convert_to_ts($key, $value, $boolisnumber, $indentation . ' ') . ';';
if (!$value instanceof external_multiple_structure || !$value->content instanceof external_single_structure) {
// Add inline comments after the field, except for arrays of objects where it's added at the start.
$result .= get_inline_comment($value->desc);
}
$result .= "\n";
}
$result .= "$indentation}";
return $result;
} else if ($value instanceof external_multiple_structure) {
// It's an array.
$result = convert_key_type($key, '', $value->required, $indentation);
$result .= convert_to_ts(null, $value->content, $boolisnumber, $indentation, $value->desc);
$result .= "[]";
return $result;
} else {
echo "WARNING: Unknown structure: $key " . get_class($value) . " \n";
return "";
}
}
/**
* Concatenate two paths.
*/
function concatenate_paths($left, $right, $separator = '/') {
if (!is_string($left) || $left == '') {
return $right;
} else if (!is_string($right) || $right == '') {
return $left;
}
$lastCharLeft = substr($left, -1);
$firstCharRight = $right[0];
if ($lastCharLeft === $separator && $firstCharRight === $separator) {
return $left . substr($right, 1);
} else if ($lastCharLeft !== $separator && $firstCharRight !== '/') {
return $left . '/' . $right;
} else {
return $left . $right;
}
}
/**
* Detect changes between 2 WS structures. We only detect fields that have been added or modified, not removed fields.
*/
function detect_ws_changes($new, $old, $key = '', $path = '') {
$messages = [];
if (gettype($new) != gettype($old)) {
// The type has changed.
$messages[] = "Property '$key' has changed type, from '" . gettype($old) . "' to '" . gettype($new) .
($path != '' ? "' inside $path." : "'.");
} else if ($new instanceof external_value && $new->type != $old->type) {
// The type has changed.
$messages[] = "Property '$key' has changed type, from '" . $old->type . "' to '" . $new->type .
($path != '' ? "' inside $path." : "'.");
} else if ($new instanceof external_warnings || $new instanceof external_files) {
// Ignore these types.
} else if ($new instanceof external_single_structure) {
// Check each subproperty.
$newpath = ($path != '' ? "$path." : '') . $key;
foreach ($new->keys as $subkey => $value) {
if (!isset($old->keys[$subkey])) {
// New property.
$messages[] = "New property '$subkey' found" . ($newpath != '' ? " inside '$newpath'." : '.');
} else {
$messages = array_merge($messages, detect_ws_changes($value, $old->keys[$subkey], $subkey, $newpath));
}
}
} else if ($new instanceof external_multiple_structure) {
// Recursive call with the content.
$messages = array_merge($messages, detect_ws_changes($new->content, $old->content, $key, $path));
}
return $messages;
}
/**
* Remove all closures (anonymous functions) in the default values so the object can be serialized.
*/
function remove_default_closures($value) {
if ($value instanceof external_warnings || $value instanceof external_files) {
// Ignore these types.
} else if ($value instanceof external_value) {
if ($value->default instanceof Closure) {
$value->default = null;
}
} else if ($value instanceof external_single_structure) {
foreach ($value->keys as $key => $subvalue) {
remove_default_closures($subvalue);
}
} else if ($value instanceof external_multiple_structure) {
remove_default_closures($value->content);
}
}

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,14 +172,14 @@
</ion-item-group>
<!-- Competencies alignment -->
<ion-item-group *ngIf="badge.competencies">
<ion-item-group *ngIf="badge && badge.alignment">
<ion-item-divider>
<h2>{{ 'addon.badges.alignment' | translate}}</h2>
</ion-item-divider>
<a ion-item text-wrap *ngFor="let competency of badge.competencies" [href]="competency.targeturl" core-link auto-login="no">
<h2><core-format-text [text]="competency.targetname"></core-format-text></h2>
<a ion-item text-wrap *ngFor="let alignment of badge.alignment" [href]="alignment.targeturl" core-link auto-login="no">
<h2><core-format-text [text]="alignment.targetname"></core-format-text></h2>
</a>
<ion-item text-wrap *ngIf="badge.competencies.length == 0">
<ion-item text-wrap *ngIf="badge.alignment.length == 0">
<h2>{{ 'addon.badges.noalignment' | translate}}</h2>
</ion-item>
</ion-item-group>

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);
@ -85,8 +86,13 @@ export class AddonBadgesProvider {
updateFrequency: CoreSite.FREQUENCY_RARELY
};
return site.read('core_badges_get_user_badges', data, preSets).then((response) => {
return site.read('core_badges_get_user_badges', data, preSets).then((response: AddonBadgesGetUserBadgesResult): any => {
if (response && response.badges) {
// In 3.7, competencies was renamed to alignment. Rename the property in 3.6 too.
response.badges.forEach((badge) => {
badge.alignment = badge.alignment || badge.competencies;
});
return response.badges;
} else {
return Promise.reject(null);
@ -110,3 +116,85 @@ 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.7. Calculated by the app for 3.6 sites. 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.
}[];
competencies?: { // @deprecated from 3.7. @since 3.6. In 3.7 it was renamed to alignment.
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; // @since 3.6. 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; // @since 3.8. Visible.
fullnamedisplay: string; // Fullnamedisplay.
viewurl: string; // Viewurl.
courseimage: string; // @since 3.6. Courseimage.
progress?: number; // @since 3.6. Progress.
hasprogress: boolean; // @since 3.6. Hasprogress.
isfavourite: boolean; // @since 3.6. Isfavourite.
hidden: boolean; // @since 3.6. Hidden.
timeaccess?: number; // @since 3.6. Timeaccess.
showshortname: boolean; // @since 3.6. Showshortname.
coursecategory: string; // @since 3.7. 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; // @since 3.7. Normalisedeventtype.
normalisedeventtypetext: string; // @since 3.7. 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; // @since 3.5. 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; // @since 3.8. 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; // @since 3.7. 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; // @since 3.7. 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[]; // @since 3.7. Plans.
pluginbaseurl: string; // @since 3.7. 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; // @since 3.6. 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[]; // @since 3.7.
};

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; // @since 3.7. 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; // @since 3.8. 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; // @since 3.6. 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; // @since 3.6. 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; // @since 3.6. 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; // @since 3.7. 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; // @since 3.7. 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; // @since 3.6. 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; // @since 3.7. The component that generated the notification.
eventtype?: string; // @since 3.7. The type of notification.
customdata?: string; // @since 3.7. 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; // @since 3.6. The text of the message.
timecreated?: number; // @since 3.6. The timecreated timestamp for the message.
conversationid?: number; // @since 3.6. The conversation id for this message.
useridfrom?: number; // @since 3.6. The user id who sent the message.
candeletemessagesforallusers: boolean; // @since 3.7. 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

@ -15,6 +15,7 @@
import { Injector } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { AddonModAssignFeedbackHandler } from '../providers/feedback-delegate';
import { AddonModAssignAssign, AddonModAssignSubmission, AddonModAssignPlugin } from '../providers/assign';
/**
* Base handler for feedback plugins.
@ -48,7 +49,7 @@ export class AddonModAssignBaseFeedbackHandler implements AddonModAssignFeedback
* @param plugin The plugin object.
* @return The component (or promise resolved with component) to use, undefined if not found.
*/
getComponent(injector: Injector, plugin: any): any | Promise<any> {
getComponent(injector: Injector, plugin: AddonModAssignPlugin): any | Promise<any> {
// Nothing to do.
}
@ -74,7 +75,8 @@ export class AddonModAssignBaseFeedbackHandler implements AddonModAssignFeedback
* @param siteId Site ID. If not defined, current site.
* @return The files (or promise resolved with the files).
*/
getPluginFiles(assign: any, submission: any, plugin: any, siteId?: string): any[] | Promise<any[]> {
getPluginFiles(assign: AddonModAssignAssign, submission: AddonModAssignSubmission,
plugin: AddonModAssignPlugin, siteId?: string): any[] | Promise<any[]> {
return [];
}
@ -84,7 +86,7 @@ export class AddonModAssignBaseFeedbackHandler implements AddonModAssignFeedback
* @param plugin The plugin object.
* @return The plugin name.
*/
getPluginName(plugin: any): string {
getPluginName(plugin: AddonModAssignPlugin): string {
// Check if there's a translated string for the plugin.
const translationId = 'addon.mod_assign_feedback_' + plugin.type + '.pluginname',
translation = this.translate.instant(translationId);
@ -109,7 +111,8 @@ export class AddonModAssignBaseFeedbackHandler implements AddonModAssignFeedback
* @param inputData Data entered by the user for the feedback.
* @return Boolean (or promise resolved with boolean): whether the data has changed.
*/
hasDataChanged(assign: any, submission: any, plugin: any, inputData: any): boolean | Promise<boolean> {
hasDataChanged(assign: AddonModAssignAssign, submission: AddonModAssignSubmission,
plugin: AddonModAssignPlugin, inputData: any, userId: number): boolean | Promise<boolean> {
return false;
}
@ -144,7 +147,8 @@ export class AddonModAssignBaseFeedbackHandler implements AddonModAssignFeedback
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved when done.
*/
prefetch(assign: any, submission: any, plugin: any, siteId?: string): Promise<any> {
prefetch(assign: AddonModAssignAssign, submission: AddonModAssignSubmission,
plugin: AddonModAssignPlugin, siteId?: string): Promise<any> {
return Promise.resolve();
}
@ -158,7 +162,8 @@ export class AddonModAssignBaseFeedbackHandler implements AddonModAssignFeedback
* @param siteId Site ID. If not defined, current site.
* @return If the function is async, it should return a Promise resolved when done.
*/
prepareFeedbackData(assignId: number, userId: number, plugin: any, pluginData: any, siteId?: string): void | Promise<any> {
prepareFeedbackData(assignId: number, userId: number, plugin: AddonModAssignPlugin, pluginData: any,
siteId?: string): void | Promise<any> {
// Nothing to do.
}
@ -172,7 +177,8 @@ export class AddonModAssignBaseFeedbackHandler implements AddonModAssignFeedback
* @param siteId Site ID. If not defined, current site.
* @return If the function is async, it should return a Promise resolved when done.
*/
saveDraft(assignId: number, userId: number, plugin: any, data: any, siteId?: string): void | Promise<any> {
saveDraft(assignId: number, userId: number, plugin: AddonModAssignPlugin, data: any, siteId?: string)
: void | Promise<any> {
// Nothing to do.
}
}

View File

@ -15,6 +15,7 @@
import { Injector } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { AddonModAssignSubmissionHandler } from '../providers/submission-delegate';
import { AddonModAssignAssign, AddonModAssignSubmission, AddonModAssignPlugin } from '../providers/assign';
/**
* Base handler for submission plugins.
@ -38,7 +39,8 @@ export class AddonModAssignBaseSubmissionHandler implements AddonModAssignSubmis
* @param plugin The plugin object.
* @return Boolean or promise resolved with boolean: whether it can be edited in offline.
*/
canEditOffline(assign: any, submission: any, plugin: any): boolean | Promise<boolean> {
canEditOffline(assign: AddonModAssignAssign, submission: AddonModAssignSubmission,
plugin: AddonModAssignPlugin): boolean | Promise<boolean> {
return false;
}
@ -50,7 +52,8 @@ export class AddonModAssignBaseSubmissionHandler implements AddonModAssignSubmis
* @param plugin The plugin object.
* @param inputData Data entered by the user for the submission.
*/
clearTmpData(assign: any, submission: any, plugin: any, inputData: any): void {
clearTmpData(assign: AddonModAssignAssign, submission: AddonModAssignSubmission,
plugin: AddonModAssignPlugin, inputData: any): void {
// Nothing to do.
}
@ -65,7 +68,8 @@ export class AddonModAssignBaseSubmissionHandler implements AddonModAssignSubmis
* @param siteId Site ID. If not defined, current site.
* @return If the function is async, it should return a Promise resolved when done.
*/
copySubmissionData(assign: any, plugin: any, pluginData: any, userId?: number, siteId?: string): void | Promise<any> {
copySubmissionData(assign: AddonModAssignAssign, plugin: AddonModAssignPlugin, pluginData: any,
userId?: number, siteId?: string): void | Promise<any> {
// Nothing to do.
}
@ -79,7 +83,8 @@ export class AddonModAssignBaseSubmissionHandler implements AddonModAssignSubmis
* @param siteId Site ID. If not defined, current site.
* @return If the function is async, it should return a Promise resolved when done.
*/
deleteOfflineData(assign: any, submission: any, plugin: any, offlineData: any, siteId?: string): void | Promise<any> {
deleteOfflineData(assign: AddonModAssignAssign, submission: AddonModAssignSubmission,
plugin: AddonModAssignPlugin, offlineData: any, siteId?: string): void | Promise<any> {
// Nothing to do.
}
@ -92,7 +97,7 @@ export class AddonModAssignBaseSubmissionHandler implements AddonModAssignSubmis
* @param edit Whether the user is editing.
* @return The component (or promise resolved with component) to use, undefined if not found.
*/
getComponent(injector: Injector, plugin: any, edit?: boolean): any | Promise<any> {
getComponent(injector: Injector, plugin: AddonModAssignPlugin, edit?: boolean): any | Promise<any> {
// Nothing to do.
}
@ -106,7 +111,8 @@ export class AddonModAssignBaseSubmissionHandler implements AddonModAssignSubmis
* @param siteId Site ID. If not defined, current site.
* @return The files (or promise resolved with the files).
*/
getPluginFiles(assign: any, submission: any, plugin: any, siteId?: string): any[] | Promise<any[]> {
getPluginFiles(assign: AddonModAssignAssign, submission: AddonModAssignSubmission,
plugin: AddonModAssignPlugin, siteId?: string): any[] | Promise<any[]> {
return [];
}
@ -116,7 +122,7 @@ export class AddonModAssignBaseSubmissionHandler implements AddonModAssignSubmis
* @param plugin The plugin object.
* @return The plugin name.
*/
getPluginName(plugin: any): string {
getPluginName(plugin: AddonModAssignPlugin): string {
// Check if there's a translated string for the plugin.
const translationId = 'addon.mod_assign_submission_' + plugin.type + '.pluginname',
translation = this.translate.instant(translationId);
@ -139,7 +145,7 @@ export class AddonModAssignBaseSubmissionHandler implements AddonModAssignSubmis
* @param plugin The plugin object.
* @return The size (or promise resolved with size).
*/
getSizeForCopy(assign: any, plugin: any): number | Promise<number> {
getSizeForCopy(assign: AddonModAssignAssign, plugin: AddonModAssignPlugin): number | Promise<number> {
return 0;
}
@ -147,10 +153,12 @@ export class AddonModAssignBaseSubmissionHandler implements AddonModAssignSubmis
* Get the size of data (in bytes) this plugin will send to add or edit a submission.
*
* @param assign The assignment.
* @param submission The submission.
* @param plugin The plugin object.
* @return The size (or promise resolved with size).
*/
getSizeForEdit(assign: any, plugin: any): number | Promise<number> {
getSizeForEdit(assign: AddonModAssignAssign, submission: AddonModAssignSubmission,
plugin: AddonModAssignPlugin, inputData: any): number | Promise<number> {
return 0;
}
@ -163,7 +171,8 @@ export class AddonModAssignBaseSubmissionHandler implements AddonModAssignSubmis
* @param inputData Data entered by the user for the submission.
* @return Boolean (or promise resolved with boolean): whether the data has changed.
*/
hasDataChanged(assign: any, submission: any, plugin: any, inputData: any): boolean | Promise<boolean> {
hasDataChanged(assign: AddonModAssignAssign, submission: AddonModAssignSubmission,
plugin: AddonModAssignPlugin, inputData: any): boolean | Promise<boolean> {
return false;
}
@ -194,7 +203,8 @@ export class AddonModAssignBaseSubmissionHandler implements AddonModAssignSubmis
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved when done.
*/
prefetch(assign: any, submission: any, plugin: any, siteId?: string): Promise<any> {
prefetch?(assign: AddonModAssignAssign, submission: AddonModAssignSubmission,
plugin: AddonModAssignPlugin, siteId?: string): Promise<any> {
return Promise.resolve();
}
@ -211,7 +221,8 @@ export class AddonModAssignBaseSubmissionHandler implements AddonModAssignSubmis
* @param siteId Site ID. If not defined, current site.
* @return If the function is async, it should return a Promise resolved when done.
*/
prepareSubmissionData?(assign: any, submission: any, plugin: any, inputData: any, pluginData: any, offline?: boolean,
prepareSubmissionData(assign: AddonModAssignAssign, submission: AddonModAssignSubmission,
plugin: AddonModAssignPlugin, inputData: any, pluginData: any, offline?: boolean,
userId?: number, siteId?: string): void | Promise<any> {
// Nothing to do.
}
@ -228,8 +239,8 @@ export class AddonModAssignBaseSubmissionHandler implements AddonModAssignSubmis
* @param siteId Site ID. If not defined, current site.
* @return If the function is async, it should return a Promise resolved when done.
*/
prepareSyncData?(assign: any, submission: any, plugin: any, offlineData: any, pluginData: any, siteId?: string)
: void | Promise<any> {
prepareSyncData(assign: AddonModAssignAssign, submission: AddonModAssignSubmission,
plugin: AddonModAssignPlugin, offlineData: any, pluginData: any, siteId?: string): void | Promise<any> {
// Nothing to do.
}
}

View File

@ -14,14 +14,15 @@
import { Input } from '@angular/core';
import { ModalController } from 'ionic-angular';
import { AddonModAssignAssign, AddonModAssignSubmission, AddonModAssignPlugin } from '../providers/assign';
/**
* Base class for component to render a feedback plugin.
*/
export class AddonModAssignFeedbackPluginComponentBase {
@Input() assign: any; // The assignment.
@Input() submission: any; // The submission.
@Input() plugin: any; // The plugin object.
@Input() assign: AddonModAssignAssign; // The assignment.
@Input() submission: AddonModAssignSubmission; // The submission.
@Input() plugin: AddonModAssignPlugin; // The plugin object.
@Input() userId: number; // The user ID of the submission.
@Input() configs: any; // The configs for the plugin.
@Input() canEdit: boolean; // Whether the user can edit.

View File

@ -13,15 +13,16 @@
// limitations under the License.
import { Input } from '@angular/core';
import { AddonModAssignAssign, AddonModAssignSubmission, AddonModAssignPlugin } from '../providers/assign';
/**
* Base class for component to render a submission plugin.
*/
export class AddonModAssignSubmissionPluginComponent {
@Input() assign: any; // The assignment.
@Input() submission: any; // The submission.
@Input() plugin: any; // The plugin object.
@Input() configs: any; // The configs for the plugin.
@Input() assign: AddonModAssignAssign; // The assignment.
@Input() submission: AddonModAssignSubmission; // The submission.
@Input() plugin: AddonModAssignPlugin; // The plugin object.
@Input() configs: {[name: string]: string}; // The configs for the plugin.
@Input() edit: boolean; // Whether the user is editing.
@Input() allowOffline: boolean; // Whether to allow offline.

View File

@ -13,7 +13,9 @@
// limitations under the License.
import { Component, Input, OnInit, Injector, ViewChild } from '@angular/core';
import { AddonModAssignProvider } from '../../providers/assign';
import {
AddonModAssignProvider, AddonModAssignAssign, AddonModAssignSubmission, AddonModAssignPlugin
} from '../../providers/assign';
import { AddonModAssignHelperProvider } from '../../providers/helper';
import { AddonModAssignFeedbackDelegate } from '../../providers/feedback-delegate';
import { CoreDynamicComponent } from '@components/dynamic-component/dynamic-component';
@ -28,9 +30,9 @@ import { CoreDynamicComponent } from '@components/dynamic-component/dynamic-comp
export class AddonModAssignFeedbackPluginComponent implements OnInit {
@ViewChild(CoreDynamicComponent) dynamicComponent: CoreDynamicComponent;
@Input() assign: any; // The assignment.
@Input() submission: any; // The submission.
@Input() plugin: any; // The plugin object.
@Input() assign: AddonModAssignAssign; // The assignment.
@Input() submission: AddonModAssignSubmission; // The submission.
@Input() plugin: AddonModAssignPlugin; // The plugin object.
@Input() userId: number; // The user ID of the submission.
@Input() canEdit: boolean | string; // Whether the user can edit.
@Input() edit: boolean | string; // Whether the user is editing.

View File

@ -17,7 +17,7 @@ import { Content, NavController } from 'ionic-angular';
import { CoreGroupsProvider, CoreGroupInfo } from '@providers/groups';
import { CoreTimeUtilsProvider } from '@providers/utils/time';
import { CoreCourseModuleMainActivityComponent } from '@core/course/classes/main-activity-component';
import { AddonModAssignProvider } from '../../providers/assign';
import { AddonModAssignProvider, AddonModAssignAssign, AddonModAssignSubmissionGradingSummary } from '../../providers/assign';
import { AddonModAssignHelperProvider } from '../../providers/helper';
import { AddonModAssignOfflineProvider } from '../../providers/assign-offline';
import { AddonModAssignSyncProvider } from '../../providers/assign-sync';
@ -36,13 +36,13 @@ export class AddonModAssignIndexComponent extends CoreCourseModuleMainActivityCo
component = AddonModAssignProvider.COMPONENT;
moduleName = 'assign';
assign: any; // The assign object.
assign: AddonModAssignAssign; // The assign object.
canViewAllSubmissions: boolean; // Whether the user can view all submissions.
canViewOwnSubmission: boolean; // Whether the user can view their own submission.
timeRemaining: string; // Message about time remaining to submit.
lateSubmissions: string; // Message about late submissions.
showNumbers = true; // Whether to show number of submissions with each status.
summary: any; // The summary.
summary: AddonModAssignSubmissionGradingSummary; // The grading summary.
needsGradingAvalaible: boolean; // Whether we can see the submissions that need grading.
groupInfo: CoreGroupInfo = {
@ -153,7 +153,7 @@ export class AddonModAssignIndexComponent extends CoreCourseModuleMainActivityCo
this.assign = assignData;
this.dataRetrieved.emit(this.assign);
this.description = this.assign.intro || this.description;
this.description = this.assign.intro;
if (sync) {
// Try to synchronize the assign.

View File

@ -13,7 +13,9 @@
// limitations under the License.
import { Component, Input, OnInit, Injector, ViewChild } from '@angular/core';
import { AddonModAssignProvider } from '../../providers/assign';
import {
AddonModAssignProvider, AddonModAssignAssign, AddonModAssignSubmission, AddonModAssignPlugin
} from '../../providers/assign';
import { AddonModAssignHelperProvider } from '../../providers/helper';
import { AddonModAssignSubmissionDelegate } from '../../providers/submission-delegate';
import { CoreDynamicComponent } from '@components/dynamic-component/dynamic-component';
@ -28,9 +30,9 @@ import { CoreDynamicComponent } from '@components/dynamic-component/dynamic-comp
export class AddonModAssignSubmissionPluginComponent implements OnInit {
@ViewChild(CoreDynamicComponent) dynamicComponent: CoreDynamicComponent;
@Input() assign: any; // The assignment.
@Input() submission: any; // The submission.
@Input() plugin: any; // The plugin object.
@Input() assign: AddonModAssignAssign; // The assignment.
@Input() submission: AddonModAssignSubmission; // The submission.
@Input() plugin: AddonModAssignPlugin; // The plugin object.
@Input() edit: boolean | string; // Whether the user is editing.
@Input() allowOffline: boolean | string; // Whether to allow offline.

View File

@ -29,7 +29,10 @@ import { CoreCourseProvider } from '@core/course/providers/course';
import { CoreFileUploaderHelperProvider } from '@core/fileuploader/providers/helper';
import { CoreGradesHelperProvider } from '@core/grades/providers/helper';
import { CoreUserProvider } from '@core/user/providers/user';
import { AddonModAssignProvider } from '../../providers/assign';
import {
AddonModAssignProvider, AddonModAssignAssign, AddonModAssignSubmissionFeedback, AddonModAssignSubmission,
AddonModAssignSubmissionAttempt, AddonModAssignSubmissionPreviousAttempt, AddonModAssignPlugin
} from '../../providers/assign';
import { AddonModAssignHelperProvider } from '../../providers/helper';
import { AddonModAssignOfflineProvider } from '../../providers/assign-offline';
import { CoreTabsComponent } from '@components/tabs/tabs';
@ -55,11 +58,11 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy {
loaded: boolean; // Whether data has been loaded.
selectedTab: number; // Tab selected on start.
assign: any; // The assignment the submission belongs to.
userSubmission: any; // The submission object.
assign: AddonModAssignAssign; // The assignment the submission belongs to.
userSubmission: AddonModAssignSubmission; // The submission object.
isSubmittedForGrading: boolean; // Whether the submission has been submitted for grading.
submitModel: any = {}; // Model where to store the data to submit (for grading).
feedback: any; // The feedback.
feedback: AddonModAssignSubmissionFeedbackFormatted; // The feedback.
hasOffline: boolean; // Whether there is offline data.
submittedOffline: boolean; // Whether it was submitted in offline.
fromDate: string; // Readable date when the assign started accepting submissions.
@ -67,7 +70,7 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy {
maxAttemptsText: string; // The text for maximum attempts.
blindMarking: boolean; // Whether blind marking is enabled.
user: any; // The user.
lastAttempt: any; // The last attempt.
lastAttempt: AddonModAssignSubmissionAttemptFormatted; // The last attempt.
membersToSubmit: any[]; // Team members that need to submit the assignment.
canSubmit: boolean; // Whether the user can submit for grading.
canEdit: boolean; // Whether the user can edit the submission.
@ -77,7 +80,7 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy {
gradingStatusTranslationId: string; // Key of the text to display for the grading status.
gradingColor: string; // Color to apply to the grading status.
workflowStatusTranslationId: string; // Key of the text to display for the workflow status.
submissionPlugins: string[]; // List of submission plugins names.
submissionPlugins: AddonModAssignPlugin[]; // List of submission plugins.
timeRemaining: string; // Message about time remaining.
timeRemainingClass: string; // Class to apply to time remaining message.
statusTranslated: string; // Status.
@ -99,7 +102,7 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy {
protected siteId: string; // Current site ID.
protected currentUserId: number; // Current user ID.
protected previousAttempt: any; // The previous attempt.
protected previousAttempt: AddonModAssignSubmissionPreviousAttempt; // The previous attempt.
protected submissionStatusAvailable: boolean; // Whether we were able to retrieve the submission status.
protected originalGrades: any = {}; // Object with the original grade data, to check for changes.
protected isDestroyed: boolean; // Whether the component has been destroyed.
@ -209,7 +212,7 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy {
return this.goToEdit();
}
const previousSubmission = this.assignProvider.getSubmissionObjectFromAttempt(this.assign, this.previousAttempt);
const previousSubmission = this.previousAttempt.submission;
let modal = this.domUtils.showModalLoading();
this.assignHelper.getSubmissionSizeForCopy(this.assign, previousSubmission).catch(() => {
@ -303,7 +306,8 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy {
}
if (this.feedback && this.feedback.plugins) {
return this.assignHelper.hasFeedbackDataChanged(this.assign, this.submitId, this.feedback).catch(() => {
return this.assignHelper.hasFeedbackDataChanged(this.assign, this.userSubmission, this.feedback, this.submitId)
.catch(() => {
// Error ocurred, consider there are no changes.
return false;
});
@ -438,7 +442,7 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy {
// Check if there's any unsupported plugin for editing.
if (!this.userSubmission || !this.userSubmission.plugins) {
// Submission not created yet, we have to use assign configs to detect the plugins used.
this.userSubmission = {};
this.userSubmission = this.assignHelper.createEmptySubmission();
this.userSubmission.plugins = this.assignHelper.getPluginsEnabled(this.assign, 'assignsubmission');
}
@ -461,7 +465,7 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy {
* @param feedback The feedback data from the submission status.
* @return Promise resolved when done.
*/
protected loadFeedback(feedback: any): Promise<any> {
protected loadFeedback(feedback: AddonModAssignSubmissionFeedback): Promise<any> {
this.grade = {
method: false,
grade: false,
@ -571,7 +575,7 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy {
if (!this.feedback || !this.feedback.plugins) {
// Feedback plugins not present, we have to use assign configs to detect the plugins used.
this.feedback = {};
this.feedback = this.assignHelper.createEmptyFeedback();
this.feedback.plugins = this.assignHelper.getPluginsEnabled(this.assign, 'assignfeedback');
}
@ -885,7 +889,7 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy {
// Show error if submission statement should be shown but it couldn't be retrieved.
this.showErrorStatementEdit = submissionStatementMissing && !this.assign.submissiondrafts &&
this.submitId == this.currentUserId;
this.showErrorStatementSubmit = submissionStatementMissing && this.assign.submissiondrafts;
this.showErrorStatementSubmit = submissionStatementMissing && !!this.assign.submissiondrafts;
this.userSubmission = this.assignProvider.getSubmissionObjectFromAttempt(this.assign, response.lastattempt);
@ -954,3 +958,17 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy {
}
}
}
/**
* Submission attempt with some calculated data.
*/
type AddonModAssignSubmissionAttemptFormatted = AddonModAssignSubmissionAttempt & {
submissiongroupname?: string; // Calculated in the app. Group name the attempt belongs to.
};
/**
* Feedback of an assign submission with some calculated data.
*/
type AddonModAssignSubmissionFeedbackFormatted = AddonModAssignSubmissionFeedback & {
advancedgrade?: boolean; // Calculated in the app. Whether it uses advanced grading.
};

View File

@ -16,7 +16,9 @@
import { Injectable, Injector } from '@angular/core';
import { CoreSitesProvider } from '@providers/sites';
import { CoreTextUtilsProvider } from '@providers/utils/text';
import { AddonModAssignProvider } from '../../../providers/assign';
import {
AddonModAssignProvider, AddonModAssignAssign, AddonModAssignSubmission, AddonModAssignPlugin
} from '../../../providers/assign';
import { AddonModAssignOfflineProvider } from '../../../providers/assign-offline';
import { AddonModAssignFeedbackHandler } from '../../../providers/feedback-delegate';
import { AddonModAssignFeedbackCommentsComponent } from '../component/comments';
@ -50,14 +52,14 @@ export class AddonModAssignFeedbackCommentsHandler implements AddonModAssignFeed
}
/**
* Return the Component to use to display the plugin data, either in read or in edit mode.
* Return the Component to use to display the plugin data.
* It's recommended to return the class of the component, but you can also return an instance of the component.
*
* @param injector Injector.
* @param plugin The plugin object.
* @return The component (or promise resolved with component) to use, undefined if not found.
*/
getComponent(injector: Injector, plugin: any): any | Promise<any> {
getComponent(injector: Injector, plugin: AddonModAssignPlugin): any | Promise<any> {
return AddonModAssignFeedbackCommentsComponent;
}
@ -101,7 +103,8 @@ export class AddonModAssignFeedbackCommentsHandler implements AddonModAssignFeed
* @param siteId Site ID. If not defined, current site.
* @return The files (or promise resolved with the files).
*/
getPluginFiles(assign: any, submission: any, plugin: any, siteId?: string): any[] | Promise<any[]> {
getPluginFiles(assign: AddonModAssignAssign, submission: AddonModAssignSubmission,
plugin: AddonModAssignPlugin, siteId?: string): any[] | Promise<any[]> {
return this.assignProvider.getSubmissionPluginAttachments(plugin);
}
@ -135,7 +138,9 @@ export class AddonModAssignFeedbackCommentsHandler implements AddonModAssignFeed
* @param userId User ID of the submission.
* @return Boolean (or promise resolved with boolean): whether the data has changed.
*/
hasDataChanged(assign: any, submission: any, plugin: any, inputData: any, userId: number): boolean | Promise<boolean> {
hasDataChanged(assign: AddonModAssignAssign, submission: AddonModAssignSubmission,
plugin: AddonModAssignPlugin, inputData: any, userId: number): boolean | Promise<boolean> {
// Get it from plugin or offline.
return this.assignOfflineProvider.getSubmissionGrade(assign.id, userId).catch(() => {
// No offline data found.
@ -191,7 +196,9 @@ export class AddonModAssignFeedbackCommentsHandler implements AddonModAssignFeed
* @param siteId Site ID. If not defined, current site.
* @return If the function is async, it should return a Promise resolved when done.
*/
prepareFeedbackData(assignId: number, userId: number, plugin: any, pluginData: any, siteId?: string): void | Promise<any> {
prepareFeedbackData(assignId: number, userId: number, plugin: AddonModAssignPlugin, pluginData: any,
siteId?: string): void | Promise<any> {
const draft = this.getDraft(assignId, userId, siteId);
if (draft) {
@ -212,7 +219,9 @@ export class AddonModAssignFeedbackCommentsHandler implements AddonModAssignFeed
* @param siteId Site ID. If not defined, current site.
* @return If the function is async, it should return a Promise resolved when done.
*/
saveDraft(assignId: number, userId: number, plugin: any, data: any, siteId?: string): void | Promise<any> {
saveDraft(assignId: number, userId: number, plugin: AddonModAssignPlugin, data: any, siteId?: string)
: void | Promise<any> {
if (data) {
this.drafts[this.getDraftId(assignId, userId, siteId)] = data;
}

View File

@ -14,7 +14,9 @@
// limitations under the License.
import { Injectable, Injector } from '@angular/core';
import { AddonModAssignProvider } from '../../../providers/assign';
import {
AddonModAssignProvider, AddonModAssignAssign, AddonModAssignSubmission, AddonModAssignPlugin
} from '../../../providers/assign';
import { AddonModAssignFeedbackHandler } from '../../../providers/feedback-delegate';
import { AddonModAssignFeedbackEditPdfComponent } from '../component/editpdf';
@ -29,14 +31,14 @@ export class AddonModAssignFeedbackEditPdfHandler implements AddonModAssignFeedb
constructor(private assignProvider: AddonModAssignProvider) { }
/**
* Return the Component to use to display the plugin data, either in read or in edit mode.
* Return the Component to use to display the plugin data.
* It's recommended to return the class of the component, but you can also return an instance of the component.
*
* @param injector Injector.
* @param plugin The plugin object.
* @return The component (or promise resolved with component) to use, undefined if not found.
*/
getComponent(injector: Injector, plugin: any): any | Promise<any> {
getComponent(injector: Injector, plugin: AddonModAssignPlugin): any | Promise<any> {
return AddonModAssignFeedbackEditPdfComponent;
}
@ -50,7 +52,8 @@ export class AddonModAssignFeedbackEditPdfHandler implements AddonModAssignFeedb
* @param siteId Site ID. If not defined, current site.
* @return The files (or promise resolved with the files).
*/
getPluginFiles(assign: any, submission: any, plugin: any, siteId?: string): any[] | Promise<any[]> {
getPluginFiles(assign: AddonModAssignAssign, submission: AddonModAssignSubmission,
plugin: AddonModAssignPlugin, siteId?: string): any[] | Promise<any[]> {
return this.assignProvider.getSubmissionPluginAttachments(plugin);
}

View File

@ -14,7 +14,9 @@
// limitations under the License.
import { Injectable, Injector } from '@angular/core';
import { AddonModAssignProvider } from '../../../providers/assign';
import {
AddonModAssignProvider, AddonModAssignAssign, AddonModAssignSubmission, AddonModAssignPlugin
} from '../../../providers/assign';
import { AddonModAssignFeedbackHandler } from '../../../providers/feedback-delegate';
import { AddonModAssignFeedbackFileComponent } from '../component/file';
@ -29,14 +31,14 @@ export class AddonModAssignFeedbackFileHandler implements AddonModAssignFeedback
constructor(private assignProvider: AddonModAssignProvider) { }
/**
* Return the Component to use to display the plugin data, either in read or in edit mode.
* Return the Component to use to display the plugin data.
* It's recommended to return the class of the component, but you can also return an instance of the component.
*
* @param injector Injector.
* @param plugin The plugin object.
* @return The component (or promise resolved with component) to use, undefined if not found.
*/
getComponent(injector: Injector, plugin: any): any | Promise<any> {
getComponent(injector: Injector, plugin: AddonModAssignPlugin): any | Promise<any> {
return AddonModAssignFeedbackFileComponent;
}
@ -50,7 +52,8 @@ export class AddonModAssignFeedbackFileHandler implements AddonModAssignFeedback
* @param siteId Site ID. If not defined, current site.
* @return The files (or promise resolved with the files).
*/
getPluginFiles(assign: any, submission: any, plugin: any, siteId?: string): any[] | Promise<any[]> {
getPluginFiles(assign: AddonModAssignAssign, submission: AddonModAssignSubmission,
plugin: AddonModAssignPlugin, siteId?: string): any[] | Promise<any[]> {
return this.assignProvider.getSubmissionPluginAttachments(plugin);
}

View File

@ -17,6 +17,9 @@ import { IonicPage, ViewController, NavParams } from 'ionic-angular';
import { TranslateService } from '@ngx-translate/core';
import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { AddonModAssignFeedbackDelegate } from '../../providers/feedback-delegate';
import {
AddonModAssignAssign, AddonModAssignSubmission, AddonModAssignPlugin
} from '../../providers/assign';
/**
* Modal that allows editing a feedback plugin.
@ -28,9 +31,9 @@ import { AddonModAssignFeedbackDelegate } from '../../providers/feedback-delegat
})
export class AddonModAssignEditFeedbackModalPage {
@Input() assign: any; // The assignment.
@Input() submission: any; // The submission.
@Input() plugin: any; // The plugin object.
@Input() assign: AddonModAssignAssign; // The assignment.
@Input() submission: AddonModAssignSubmission; // The submission.
@Input() plugin: AddonModAssignPlugin; // The plugin object.
@Input() userId: number; // The user ID of the submission.
protected forceLeave = false; // To allow leaving the page without checking for changes.
@ -99,8 +102,8 @@ export class AddonModAssignEditFeedbackModalPage {
* @return Promise resolved with boolean: whether the data has changed.
*/
protected hasDataChanged(): Promise<boolean> {
return this.feedbackDelegate.hasPluginDataChanged(this.assign, this.userId, this.plugin, this.getInputData(), this.userId)
.catch(() => {
return this.feedbackDelegate.hasPluginDataChanged(this.assign, this.submission, this.plugin, this.getInputData(),
this.userId).catch(() => {
// Ignore errors.
return true;
});

View File

@ -20,7 +20,7 @@ import { CoreSitesProvider } from '@providers/sites';
import { CoreSyncProvider } from '@providers/sync';
import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { CoreFileUploaderHelperProvider } from '@core/fileuploader/providers/helper';
import { AddonModAssignProvider } from '../../providers/assign';
import { AddonModAssignProvider, AddonModAssignAssign, AddonModAssignSubmission } from '../../providers/assign';
import { AddonModAssignOfflineProvider } from '../../providers/assign-offline';
import { AddonModAssignSyncProvider } from '../../providers/assign-sync';
import { AddonModAssignHelperProvider } from '../../providers/helper';
@ -35,9 +35,9 @@ import { AddonModAssignHelperProvider } from '../../providers/helper';
})
export class AddonModAssignEditPage implements OnInit, OnDestroy {
title: string; // Title to display.
assign: any; // Assignment.
assign: AddonModAssignAssign; // Assignment.
courseId: number; // Course ID the assignment belongs to.
userSubmission: any; // The user submission.
userSubmission: AddonModAssignSubmission; // The user submission.
allowOffline: boolean; // Whether offline is allowed.
submissionStatement: string; // The submission statement.
submissionStatementAccepted: boolean; // Whether submission statement is accepted.
@ -129,7 +129,7 @@ export class AddonModAssignEditPage implements OnInit, OnDestroy {
const userSubmission = this.assignProvider.getSubmissionObjectFromAttempt(this.assign, response.lastattempt);
// Check if the user can edit it in offline.
return this.assignHelper.canEditSubmissionOffline(this.assign, userSubmission).then((canEditOffline) => {
return this.assignHelper.canEditSubmissionOffline(this.assign, userSubmission).then((canEditOffline): any => {
if (canEditOffline) {
return response;
}
@ -301,7 +301,7 @@ export class AddonModAssignEditPage implements OnInit, OnDestroy {
} else {
// Try to send it to server.
promise = this.assignProvider.saveSubmission(this.assign.id, this.courseId, pluginData, this.allowOffline,
this.userSubmission.timemodified, this.assign.submissiondrafts, this.userId);
this.userSubmission.timemodified, !!this.assign.submissiondrafts, this.userId);
}
return promise.then(() => {

View File

@ -19,9 +19,11 @@ import { CoreEventsProvider } from '@providers/events';
import { CoreSitesProvider } from '@providers/sites';
import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { CoreGroupsProvider, CoreGroupInfo } from '@providers/groups';
import { AddonModAssignProvider } from '../../providers/assign';
import {
AddonModAssignProvider, AddonModAssignAssign, AddonModAssignGrade, AddonModAssignSubmission
} from '../../providers/assign';
import { AddonModAssignOfflineProvider } from '../../providers/assign-offline';
import { AddonModAssignHelperProvider } from '../../providers/helper';
import { AddonModAssignHelperProvider, AddonModAssignSubmissionFormatted } from '../../providers/helper';
import { CoreSplitViewComponent } from '@components/split-view/split-view';
/**
@ -36,7 +38,7 @@ export class AddonModAssignSubmissionListPage implements OnInit, OnDestroy {
@ViewChild(CoreSplitViewComponent) splitviewCtrl: CoreSplitViewComponent;
title: string; // Title to display.
assign: any; // Assignment.
assign: AddonModAssignAssign; // Assignment.
submissions: any[]; // List of submissions
loaded: boolean; // Whether data has been loaded.
haveAllParticipants: boolean; // Whether all participants have been loaded.
@ -53,7 +55,7 @@ export class AddonModAssignSubmissionListPage implements OnInit, OnDestroy {
protected courseId: number; // Course ID the assignment belongs to.
protected selectedStatus: string; // The status to see.
protected gradedObserver; // Observer to refresh data when a grade changes.
protected submissionsData: any;
protected submissionsData: {canviewsubmissions: boolean, submissions?: AddonModAssignSubmission[]};
constructor(navParams: NavParams, protected sitesProvider: CoreSitesProvider, eventsProvider: CoreEventsProvider,
protected domUtils: CoreDomUtilsProvider, protected translate: TranslateService,
@ -161,14 +163,14 @@ export class AddonModAssignSubmissionListPage implements OnInit, OnDestroy {
!this.assign.markingworkflow ? this.assignProvider.getAssignmentGrades(this.assign.id) : Promise.resolve(null),
];
return Promise.all(promises).then(([submissions, grades]) => {
return Promise.all(promises).then(([submissions, grades]: [AddonModAssignSubmissionFormatted[], AddonModAssignGrade[]]) => {
// Filter the submissions to get only the ones with the right status and add some extra data.
const getNeedGrading = this.selectedStatus == AddonModAssignProvider.NEED_GRADING,
searchStatus = getNeedGrading ? AddonModAssignProvider.SUBMISSION_STATUS_SUBMITTED : this.selectedStatus,
promises = [],
showSubmissions = [];
submissions.forEach((submission) => {
submissions.forEach((submission: AddonModAssignSubmissionForList) => {
if (!searchStatus || searchStatus == submission.status) {
promises.push(this.assignOfflineProvider.getSubmissionGrade(this.assign.id, submission.userid).catch(() => {
// Ignore errors.
@ -213,7 +215,7 @@ export class AddonModAssignSubmissionListPage implements OnInit, OnDestroy {
submission.statusTranslated = this.translate.instant('addon.mod_assign.submissionstatus_' +
submission.status);
} else {
submission.statusTranslated = false;
submission.statusTranslated = '';
}
if (notSynced) {
@ -224,7 +226,7 @@ export class AddonModAssignSubmissionListPage implements OnInit, OnDestroy {
submission.gradingStatusTranslationId =
this.assignProvider.getSubmissionGradingStatusTranslationId(submission.gradingstatus);
} else {
submission.gradingStatusTranslationId = false;
submission.gradingStatusTranslationId = '';
}
showSubmissions.push(submission);
@ -299,3 +301,13 @@ export class AddonModAssignSubmissionListPage implements OnInit, OnDestroy {
this.gradedObserver && this.gradedObserver.off();
}
}
/**
* Calculated data for an assign submission.
*/
type AddonModAssignSubmissionForList = AddonModAssignSubmissionFormatted & {
statusColor?: string; // Calculated in the app. Color of the submission status.
gradingColor?: string; // Calculated in the app. Color of the submission grading status.
statusTranslated?: string; // Calculated in the app. Translated text of the submission status.
gradingStatusTranslationId?: string; // Calculated in the app. Key of the text of the submission grading status.
};

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 { CoreCourseProvider } from '@core/course/providers/course';
import { AddonModAssignProvider } from '../../providers/assign';
import { AddonModAssignProvider, AddonModAssignAssign } from '../../providers/assign';
import { AddonModAssignSubmissionComponent } from '../../components/submission/submission';
/**
@ -40,7 +40,7 @@ export class AddonModAssignSubmissionReviewPage implements OnInit {
loaded: boolean; // Whether data has been loaded.
canSaveGrades: boolean; // Whether the user can save grades.
protected assign: any; // The assignment the submission belongs to.
protected assign: AddonModAssignAssign; // The assignment the submission belongs to.
protected blindMarking: boolean; // Whether it uses blind marking.
protected forceLeave = false; // To allow leaving the page without checking for changes.

View File

@ -26,7 +26,7 @@ import { CoreCourseProvider } from '@core/course/providers/course';
import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper';
import { CoreGradesHelperProvider } from '@core/grades/providers/helper';
import { CoreSyncBaseProvider } from '@classes/base-sync';
import { AddonModAssignProvider } from './assign';
import { AddonModAssignProvider, AddonModAssignAssign } from './assign';
import { AddonModAssignOfflineProvider } from './assign-offline';
import { AddonModAssignSubmissionDelegate } from './submission-delegate';
@ -169,14 +169,14 @@ export class AddonModAssignSyncProvider extends CoreSyncBaseProvider {
syncAssign(assignId: number, siteId?: string): Promise<AddonModAssignSyncResult> {
siteId = siteId || this.sitesProvider.getCurrentSiteId();
const promises = [],
const promises: Promise<any>[] = [],
result: AddonModAssignSyncResult = {
warnings: [],
updated: false
};
let assign,
courseId,
syncPromise;
let assign: AddonModAssignAssign,
courseId: number,
syncPromise: Promise<any>;
if (this.isSyncing(assignId, siteId)) {
// There's already a sync ongoing for this assign, return the promise.
@ -269,7 +269,7 @@ export class AddonModAssignSyncProvider extends CoreSyncBaseProvider {
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved if success, rejected otherwise.
*/
protected syncSubmission(assign: any, offlineData: any, warnings: string[], siteId?: string): Promise<any> {
protected syncSubmission(assign: AddonModAssignAssign, offlineData: any, warnings: string[], siteId?: string): Promise<any> {
const userId = offlineData.userid,
pluginData = {};
let discardError,
@ -358,8 +358,8 @@ export class AddonModAssignSyncProvider extends CoreSyncBaseProvider {
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved if success, rejected otherwise.
*/
protected syncSubmissionGrade(assign: any, offlineData: any, warnings: string[], courseId: number, siteId?: string)
: Promise<any> {
protected syncSubmissionGrade(assign: AddonModAssignAssign, offlineData: any, warnings: string[], courseId: number,
siteId?: string): Promise<any> {
const userId = offlineData.userid;
let discardError;

View File

@ -27,6 +27,7 @@ import { AddonModAssignSubmissionDelegate } from './submission-delegate';
import { AddonModAssignOfflineProvider } from './assign-offline';
import { CoreSite, CoreSiteWSPreSets } from '@classes/site';
import { CoreInterceptor } from '@classes/interceptor';
import { CoreWSExternalWarning, CoreWSExternalFile } from '@providers/ws';
/**
* Service that provides some functions for assign.
@ -123,7 +124,7 @@ export class AddonModAssignProvider {
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved with the assignment.
*/
getAssignment(courseId: number, cmId: number, ignoreCache?: boolean, siteId?: string): Promise<any> {
getAssignment(courseId: number, cmId: number, ignoreCache?: boolean, siteId?: string): Promise<AddonModAssignAssign> {
return this.getAssignmentByField(courseId, 'cmid', cmId, ignoreCache, siteId);
}
@ -138,7 +139,7 @@ export class AddonModAssignProvider {
* @return Promise resolved when the assignment is retrieved.
*/
protected getAssignmentByField(courseId: number, key: string, value: any, ignoreCache?: boolean, siteId?: string)
: Promise<any> {
: Promise<AddonModAssignAssign> {
return this.sitesProvider.getSite(siteId).then((site) => {
const params = {
@ -161,7 +162,7 @@ export class AddonModAssignProvider {
delete params.includenotenrolledcourses;
return site.read('mod_assign_get_assignments', params, preSets);
}).then((response) => {
}).then((response: AddonModAssignGetAssignmentsResult): any => {
// Search the assignment to return.
if (response.courses && response.courses.length) {
const assignments = response.courses[0].assignments;
@ -187,7 +188,7 @@ export class AddonModAssignProvider {
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved with the assignment.
*/
getAssignmentById(courseId: number, id: number, ignoreCache?: boolean, siteId?: string): Promise<any> {
getAssignmentById(courseId: number, id: number, ignoreCache?: boolean, siteId?: string): Promise<AddonModAssignAssign> {
return this.getAssignmentByField(courseId, 'id', id, ignoreCache, siteId);
}
@ -225,7 +226,9 @@ export class AddonModAssignProvider {
preSets.emergencyCache = false;
}
return site.read('mod_assign_get_user_mappings', params, preSets).then((response) => {
return site.read('mod_assign_get_user_mappings', params, preSets)
.then((response: AddonModAssignGetUserMappingsResult): any => {
// Search the user.
if (response.assignments && response.assignments.length) {
if (!userId || userId < 0) {
@ -271,7 +274,7 @@ export class AddonModAssignProvider {
* @param siteId Site ID. If not defined, current site.
* @return Resolved with requested info when done.
*/
getAssignmentGrades(assignId: number, ignoreCache?: boolean, siteId?: string): Promise<any> {
getAssignmentGrades(assignId: number, ignoreCache?: boolean, siteId?: string): Promise<AddonModAssignGrade[]> {
return this.sitesProvider.getSite(siteId).then((site) => {
const params = {
assignmentids: [assignId]
@ -285,7 +288,7 @@ export class AddonModAssignProvider {
preSets.emergencyCache = false;
}
return site.read('mod_assign_get_grades', params, preSets).then((response) => {
return site.read('mod_assign_get_grades', params, preSets).then((response: AddonModAssignGetGradesResult): any => {
// Search the assignment.
if (response.assignments && response.assignments.length) {
const assignment = response.assignments[0];
@ -294,7 +297,7 @@ export class AddonModAssignProvider {
return assignment.grades;
}
} else if (response.warnings && response.warnings.length) {
if (response.warnings[0].warningcode == 3) {
if (response.warnings[0].warningcode == '3') {
// No grades found.
return [];
}
@ -362,7 +365,9 @@ export class AddonModAssignProvider {
* @param attempt Attempt.
* @return Submission object or null.
*/
getSubmissionObjectFromAttempt(assign: any, attempt: any): any {
getSubmissionObjectFromAttempt(assign: AddonModAssignAssign, attempt: AddonModAssignSubmissionAttempt)
: AddonModAssignSubmission {
if (!attempt) {
return null;
}
@ -432,7 +437,7 @@ export class AddonModAssignProvider {
* @return Promise resolved when done.
*/
getSubmissions(assignId: number, ignoreCache?: boolean, siteId?: string)
: Promise<{canviewsubmissions: boolean, submissions?: any[]}> {
: Promise<{canviewsubmissions: boolean, submissions?: AddonModAssignSubmission[]}> {
return this.sitesProvider.getSite(siteId).then((site) => {
const params = {
@ -448,9 +453,11 @@ export class AddonModAssignProvider {
preSets.emergencyCache = false;
}
return site.read('mod_assign_get_submissions', params, preSets).then((response): any => {
return site.read('mod_assign_get_submissions', params, preSets)
.then((response: AddonModAssignGetSubmissionsResult): any => {
// Check if we can view submissions, with enough permissions.
if (response.warnings.length > 0 && response.warnings[0].warningcode == 1) {
if (response.warnings.length > 0 && response.warnings[0].warningcode == '1') {
return {canviewsubmissions: false};
}
@ -489,7 +496,7 @@ export class AddonModAssignProvider {
* @return Promise always resolved with the user submission status.
*/
getSubmissionStatus(assignId: number, userId?: number, groupId?: number, isBlind?: boolean, filter: boolean = true,
ignoreCache?: boolean, siteId?: string): Promise<any> {
ignoreCache?: boolean, siteId?: string): Promise<AddonModAssignGetSubmissionStatusResult> {
userId = userId || 0;
@ -540,7 +547,7 @@ export class AddonModAssignProvider {
* @return Promise always resolved with the user submission status.
*/
getSubmissionStatusWithRetry(assign: any, userId?: number, groupId?: number, isBlind?: boolean, filter: boolean = true,
ignoreCache?: boolean, siteId?: string): Promise<any> {
ignoreCache?: boolean, siteId?: string): Promise<AddonModAssignGetSubmissionStatusResult> {
return this.getSubmissionStatus(assign.id, userId, groupId, isBlind, filter, ignoreCache, siteId).then((response) => {
const userSubmission = this.getSubmissionObjectFromAttempt(assign, response.lastattempt);
@ -630,7 +637,9 @@ export class AddonModAssignProvider {
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved with the list of participants and summary of submissions.
*/
listParticipants(assignId: number, groupId?: number, ignoreCache?: boolean, siteId?: string): Promise<any[]> {
listParticipants(assignId: number, groupId?: number, ignoreCache?: boolean, siteId?: string)
: Promise<AddonModAssignParticipant[]> {
groupId = groupId || 0;
return this.sitesProvider.getSite(siteId).then((site) => {
@ -1051,14 +1060,14 @@ export class AddonModAssignProvider {
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved when saved, rejected otherwise.
*/
saveSubmissionOnline(assignId: number, pluginData: any, siteId?: string): Promise<any> {
saveSubmissionOnline(assignId: number, pluginData: any, siteId?: string): Promise<void> {
return this.sitesProvider.getSite(siteId).then((site) => {
const params = {
assignmentid: assignId,
plugindata: pluginData
};
return site.write('mod_assign_save_submission', params).then((warnings) => {
return site.write('mod_assign_save_submission', params).then((warnings: CoreWSExternalWarning[]) => {
if (warnings && warnings.length) {
// The WebService returned warnings, reject.
return Promise.reject(warnings[0]);
@ -1120,14 +1129,14 @@ export class AddonModAssignProvider {
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved when submitted, rejected otherwise.
*/
submitForGradingOnline(assignId: number, acceptStatement: boolean, siteId?: string): Promise<any> {
submitForGradingOnline(assignId: number, acceptStatement: boolean, siteId?: string): Promise<void> {
return this.sitesProvider.getSite(siteId).then((site) => {
const params = {
assignmentid: assignId,
acceptsubmissionstatement: acceptStatement ? 1 : 0
};
return site.write('mod_assign_submit_for_grading', params).then((warnings) => {
return site.write('mod_assign_submit_for_grading', params).then((warnings: CoreWSExternalWarning[]) => {
if (warnings && warnings.length) {
// The WebService returned warnings, reject.
return Promise.reject(warnings[0]);
@ -1169,7 +1178,10 @@ export class AddonModAssignProvider {
return this.isGradingOfflineEnabled(siteId).then((enabled) => {
if (!enabled) {
return this.submitGradingFormOnline(assignId, userId, grade, attemptNumber, addAttempt, workflowState,
applyToAll, outcomes, pluginData, siteId);
applyToAll, outcomes, pluginData, siteId).then(() => {
return true;
});
}
if (!this.appProvider.isOnline()) {
@ -1212,7 +1224,7 @@ export class AddonModAssignProvider {
* @return Promise resolved when submitted, rejected otherwise.
*/
submitGradingFormOnline(assignId: number, userId: number, grade: number, attemptNumber: number, addAttempt: boolean,
workflowState: string, applyToAll: boolean, outcomes: any, pluginData: any, siteId?: string): Promise<any> {
workflowState: string, applyToAll: boolean, outcomes: any, pluginData: any, siteId?: string): Promise<void | null> {
return this.sitesProvider.getSite(siteId).then((site) => {
userId = userId || site.getUserId();
@ -1243,7 +1255,7 @@ export class AddonModAssignProvider {
jsonformdata: JSON.stringify(serialized)
};
return site.write('mod_assign_submit_grading_form', params).then((warnings) => {
return site.write('mod_assign_submit_grading_form', params).then((warnings: CoreWSExternalWarning[]) => {
if (warnings && warnings.length) {
// The WebService returned warnings, reject.
return Promise.reject(warnings[0]);
@ -1271,3 +1283,285 @@ export class AddonModAssignProvider {
});
}
}
/**
* Assign data returned by mod_assign_get_assignments.
*/
export type AddonModAssignAssign = {
id: number; // Assignment id.
cmid: number; // Course module id.
course: number; // Course id.
name: string; // Assignment name.
nosubmissions: number; // No submissions.
submissiondrafts: number; // Submissions drafts.
sendnotifications: number; // Send notifications.
sendlatenotifications: number; // Send notifications.
sendstudentnotifications: number; // Send student notifications (default).
duedate: number; // Assignment due date.
allowsubmissionsfromdate: number; // Allow submissions from date.
grade: number; // Grade type.
timemodified: number; // Last time assignment was modified.
completionsubmit: number; // If enabled, set activity as complete following submission.
cutoffdate: number; // Date after which submission is not accepted without an extension.
gradingduedate?: number; // @since 3.3. The expected date for marking the submissions.
teamsubmission: number; // If enabled, students submit as a team.
requireallteammemberssubmit: number; // If enabled, all team members must submit.
teamsubmissiongroupingid: number; // The grouping id for the team submission groups.
blindmarking: number; // If enabled, hide identities until reveal identities actioned.
hidegrader?: number; // @since 3.7. If enabled, hide grader to student.
revealidentities: number; // Show identities for a blind marking assignment.
attemptreopenmethod: string; // Method used to control opening new attempts.
maxattempts: number; // Maximum number of attempts allowed.
markingworkflow: number; // Enable marking workflow.
markingallocation: number; // Enable marking allocation.
requiresubmissionstatement: number; // Student must accept submission statement.
preventsubmissionnotingroup?: number; // @since 3.2. Prevent submission not in group.
submissionstatement?: string; // @since 3.2. Submission statement formatted.
submissionstatementformat?: number; // @since 3.2. Submissionstatement format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
configs: AddonModAssignConfig[]; // Configuration settings.
intro?: string; // Assignment intro, not allways returned because it deppends on the activity configuration.
introformat?: number; // Intro format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
introfiles?: CoreWSExternalFile[]; // @since 3.2.
introattachments?: CoreWSExternalFile[];
};
/**
* Config setting in an assign.
*/
export type AddonModAssignConfig = {
id?: number; // Assign_plugin_config id.
assignment?: number; // Assignment id.
plugin: string; // Plugin.
subtype: string; // Subtype.
name: string; // Name.
value: string; // Value.
};
/**
* Grade of an assign, returned by mod_assign_get_grades.
*/
export type AddonModAssignGrade = {
id: number; // Grade id.
assignment?: number; // Assignment id.
userid: number; // Student id.
attemptnumber: number; // Attempt number.
timecreated: number; // Grade creation time.
timemodified: number; // Grade last modified time.
grader: number; // Grader, -1 if grader is hidden.
grade: string; // Grade.
gradefordisplay?: string; // Grade rendered into a format suitable for display.
};
/**
* Assign submission returned by mod_assign_get_submissions.
*/
export type AddonModAssignSubmission = {
id: number; // Submission id.
userid: number; // Student id.
attemptnumber: number; // Attempt number.
timecreated: number; // Submission creation time.
timemodified: number; // Submission last modified time.
status: string; // Submission status.
groupid: number; // Group id.
assignment?: number; // Assignment id.
latest?: number; // Latest attempt.
plugins?: AddonModAssignPlugin[]; // Plugins.
gradingstatus?: string; // @since 3.2. Grading status.
};
/**
* Assign plugin.
*/
export type AddonModAssignPlugin = {
type: string; // Submission plugin type.
name: string; // Submission plugin name.
fileareas?: { // Fileareas.
area: string; // File area.
files?: CoreWSExternalFile[];
}[];
editorfields?: { // Editorfields.
name: string; // Field name.
description: string; // Field description.
text: string; // Field value.
format: number; // Text format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
}[];
};
/**
* Grading summary of an assign submission.
*/
export type AddonModAssignSubmissionGradingSummary = {
participantcount: number; // Number of users who can submit.
submissiondraftscount: number; // Number of submissions in draft status.
submissionsenabled: boolean; // Whether submissions are enabled or not.
submissionssubmittedcount: number; // Number of submissions in submitted status.
submissionsneedgradingcount: number; // Number of submissions that need grading.
warnofungroupedusers: string; // Whether we need to warn people about groups.
};
/**
* Attempt of an assign submission.
*/
export type AddonModAssignSubmissionAttempt = {
submission?: AddonModAssignSubmission; // Submission info.
teamsubmission?: AddonModAssignSubmission; // Submission info.
submissiongroup?: number; // The submission group id (for group submissions only).
submissiongroupmemberswhoneedtosubmit?: number[]; // List of users who still need to submit (for group submissions only).
submissionsenabled: boolean; // Whether submissions are enabled or not.
locked: boolean; // Whether new submissions are locked.
graded: boolean; // Whether the submission is graded.
canedit: boolean; // Whether the user can edit the current submission.
caneditowner?: boolean; // @since 3.2. Whether the owner of the submission can edit it.
cansubmit: boolean; // Whether the user can submit.
extensionduedate: number; // Extension due date.
blindmarking: boolean; // Whether blind marking is enabled.
gradingstatus: string; // Grading status.
usergroups: number[]; // User groups in the course.
};
/**
* Previous attempt of an assign submission.
*/
export type AddonModAssignSubmissionPreviousAttempt = {
attemptnumber: number; // Attempt number.
submission?: AddonModAssignSubmission; // Submission info.
grade?: AddonModAssignGrade; // Grade information.
feedbackplugins?: AddonModAssignPlugin[]; // Feedback info.
};
/**
* Feedback of an assign submission.
*/
export type AddonModAssignSubmissionFeedback = {
grade: AddonModAssignGrade; // Grade information.
gradefordisplay: string; // Grade rendered into a format suitable for display.
gradeddate: number; // The date the user was graded.
plugins?: AddonModAssignPlugin[]; // Plugins info.
};
/**
* Participant returned by mod_assign_list_participants.
*/
export type AddonModAssignParticipant = {
id: number; // ID of the user.
username?: string; // The username.
firstname?: string; // The first name(s) of the user.
lastname?: string; // The family name of the user.
fullname: string; // The fullname of the user.
email?: string; // Email address.
address?: string; // Postal address.
phone1?: string; // Phone 1.
phone2?: string; // Phone 2.
icq?: string; // Icq number.
skype?: string; // Skype id.
yahoo?: string; // Yahoo id.
aim?: string; // Aim id.
msn?: string; // Msn number.
department?: string; // Department.
institution?: string; // Institution.
idnumber?: string; // The idnumber of the user.
interests?: string; // User interests (separated by commas).
firstaccess?: number; // First access to the site (0 if never).
lastaccess?: number; // Last access to the site (0 if never).
suspended?: boolean; // @since 3.2. Suspend user account, either false to enable user login or true to disable it.
description?: string; // User profile description.
descriptionformat?: number; // Int format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
city?: string; // Home city of the user.
url?: string; // URL of the user.
country?: string; // Home country code of the user, such as AU or CZ.
profileimageurlsmall?: string; // User image profile URL - small version.
profileimageurl?: string; // User image profile URL - big version.
customfields?: { // User custom fields (also known as user profile fields).
type: string; // The type of the custom field - text field, checkbox...
value: string; // The value of the custom field.
name: string; // The name of the custom field.
shortname: string; // The shortname of the custom field - to be able to build the field class in the code.
}[];
preferences?: { // Users preferences.
name: string; // The name of the preferences.
value: string; // The value of the preference.
}[];
recordid?: number; // @since 3.7. Record id.
groups?: { // User groups.
id: number; // Group id.
name: string; // Group name.
description: string; // Group description.
}[];
roles?: { // User roles.
roleid: number; // Role id.
name: string; // Role name.
shortname: string; // Role shortname.
sortorder: number; // Role sortorder.
}[];
enrolledcourses?: { // Courses where the user is enrolled - limited by which courses the user is able to see.
id: number; // Id of the course.
fullname: string; // Fullname of the course.
shortname: string; // Shortname of the course.
}[];
submitted: boolean; // Have they submitted their assignment.
requiregrading: boolean; // Is their submission waiting for grading.
grantedextension?: boolean; // @since 3.3. Have they been granted an extension.
groupid?: number; // For group assignments this is the group id.
groupname?: string; // For group assignments this is the group name.
};
/**
* Result of WS mod_assign_get_assignments.
*/
export type AddonModAssignGetAssignmentsResult = {
courses: { // List of courses.
id: number; // Course id.
fullname: string; // Course full name.
shortname: string; // Course short name.
timemodified: number; // Last time modified.
assignments: AddonModAssignAssign[]; // Assignment info.
}[];
warnings?: CoreWSExternalWarning[];
};
/**
* Result of WS mod_assign_get_user_mappings.
*/
export type AddonModAssignGetUserMappingsResult = {
assignments: { // List of assign user mapping data.
assignmentid: number; // Assignment id.
mappings: {
id: number; // User mapping id.
userid: number; // Student id.
}[];
}[];
warnings?: CoreWSExternalWarning[];
};
/**
* Result of WS mod_assign_get_grades.
*/
export type AddonModAssignGetGradesResult = {
assignments: { // List of assignment grade information.
assignmentid: number; // Assignment id.
grades: AddonModAssignGrade[];
}[];
warnings?: CoreWSExternalWarning[];
};
/**
* Result of WS mod_assign_get_submissions.
*/
export type AddonModAssignGetSubmissionsResult = {
assignments: { // Assignment submissions.
assignmentid: number; // Assignment id.
submissions: AddonModAssignSubmission[];
}[];
warnings?: CoreWSExternalWarning[];
};
/**
* Result of WS mod_assign_get_submission_status.
*/
export type AddonModAssignGetSubmissionStatusResult = {
gradingsummary?: AddonModAssignSubmissionGradingSummary; // Grading information.
lastattempt?: AddonModAssignSubmissionAttempt; // Last attempt information.
feedback?: AddonModAssignSubmissionFeedback; // Feedback for the last attempt.
previousattempts?: AddonModAssignSubmissionPreviousAttempt[]; // List all the previous attempts did by the user.
warnings?: CoreWSExternalWarning[];
};

View File

@ -18,6 +18,7 @@ import { CoreEventsProvider } from '@providers/events';
import { CoreSitesProvider } from '@providers/sites';
import { CoreDelegate, CoreDelegateHandler } from '@classes/delegate';
import { AddonModAssignDefaultFeedbackHandler } from './default-feedback-handler';
import { AddonModAssignAssign, AddonModAssignSubmission, AddonModAssignPlugin } from './assign';
/**
* Interface that all feedback handlers must implement.
@ -47,7 +48,7 @@ export interface AddonModAssignFeedbackHandler extends CoreDelegateHandler {
* @param plugin The plugin object.
* @return The component (or promise resolved with component) to use, undefined if not found.
*/
getComponent?(injector: Injector, plugin: any): any | Promise<any>;
getComponent?(injector: Injector, plugin: AddonModAssignPlugin): any | Promise<any>;
/**
* Return the draft saved data of the feedback plugin.
@ -69,7 +70,8 @@ export interface AddonModAssignFeedbackHandler extends CoreDelegateHandler {
* @param siteId Site ID. If not defined, current site.
* @return The files (or promise resolved with the files).
*/
getPluginFiles?(assign: any, submission: any, plugin: any, siteId?: string): any[] | Promise<any[]>;
getPluginFiles?(assign: AddonModAssignAssign, submission: AddonModAssignSubmission,
plugin: AddonModAssignPlugin, siteId?: string): any[] | Promise<any[]>;
/**
* Get a readable name to use for the plugin.
@ -77,7 +79,7 @@ export interface AddonModAssignFeedbackHandler extends CoreDelegateHandler {
* @param plugin The plugin object.
* @return The plugin name.
*/
getPluginName?(plugin: any): string;
getPluginName?(plugin: AddonModAssignPlugin): string;
/**
* Check if the feedback data has changed for this plugin.
@ -89,7 +91,8 @@ export interface AddonModAssignFeedbackHandler extends CoreDelegateHandler {
* @param userId User ID of the submission.
* @return Boolean (or promise resolved with boolean): whether the data has changed.
*/
hasDataChanged?(assign: any, submission: any, plugin: any, inputData: any, userId: number): boolean | Promise<boolean>;
hasDataChanged?(assign: AddonModAssignAssign, submission: AddonModAssignSubmission,
plugin: AddonModAssignPlugin, inputData: any, userId: number): boolean | Promise<boolean>;
/**
* Check whether the plugin has draft data stored.
@ -111,7 +114,8 @@ export interface AddonModAssignFeedbackHandler extends CoreDelegateHandler {
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved when done.
*/
prefetch?(assign: any, submission: any, plugin: any, siteId?: string): Promise<any>;
prefetch?(assign: AddonModAssignAssign, submission: AddonModAssignSubmission,
plugin: AddonModAssignPlugin, siteId?: string): Promise<any>;
/**
* Prepare and add to pluginData the data to send to the server based on the draft data saved.
@ -123,7 +127,8 @@ export interface AddonModAssignFeedbackHandler extends CoreDelegateHandler {
* @param siteId Site ID. If not defined, current site.
* @return If the function is async, it should return a Promise resolved when done.
*/
prepareFeedbackData?(assignId: number, userId: number, plugin: any, pluginData: any, siteId?: string): void | Promise<any>;
prepareFeedbackData?(assignId: number, userId: number, plugin: AddonModAssignPlugin, pluginData: any,
siteId?: string): void | Promise<any>;
/**
* Save draft data of the feedback plugin.
@ -135,7 +140,8 @@ export interface AddonModAssignFeedbackHandler extends CoreDelegateHandler {
* @param siteId Site ID. If not defined, current site.
* @return If the function is async, it should return a Promise resolved when done.
*/
saveDraft?(assignId: number, userId: number, plugin: any, data: any, siteId?: string): void | Promise<any>;
saveDraft?(assignId: number, userId: number, plugin: AddonModAssignPlugin, data: any, siteId?: string)
: void | Promise<any>;
}
/**
@ -160,7 +166,8 @@ export class AddonModAssignFeedbackDelegate extends CoreDelegate {
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved when done.
*/
discardPluginFeedbackData(assignId: number, userId: number, plugin: any, siteId?: string): Promise<any> {
discardPluginFeedbackData(assignId: number, userId: number, plugin: AddonModAssignPlugin, siteId?: string)
: Promise<any> {
return Promise.resolve(this.executeFunctionOnEnabled(plugin.type, 'discardDraft', [assignId, userId, siteId]));
}
@ -171,7 +178,7 @@ export class AddonModAssignFeedbackDelegate extends CoreDelegate {
* @param plugin The plugin object.
* @return Promise resolved with the component to use, undefined if not found.
*/
getComponentForPlugin(injector: Injector, plugin: any): Promise<any> {
getComponentForPlugin(injector: Injector, plugin: AddonModAssignPlugin): Promise<any> {
return Promise.resolve(this.executeFunctionOnEnabled(plugin.type, 'getComponent', [injector, plugin]));
}
@ -184,7 +191,8 @@ export class AddonModAssignFeedbackDelegate extends CoreDelegate {
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved with the draft data.
*/
getPluginDraftData(assignId: number, userId: number, plugin: any, siteId?: string): Promise<any> {
getPluginDraftData(assignId: number, userId: number, plugin: AddonModAssignPlugin, siteId?: string)
: Promise<any> {
return Promise.resolve(this.executeFunctionOnEnabled(plugin.type, 'getDraft', [assignId, userId, siteId]));
}
@ -198,7 +206,8 @@ export class AddonModAssignFeedbackDelegate extends CoreDelegate {
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved with the files.
*/
getPluginFiles(assign: any, submission: any, plugin: any, siteId?: string): Promise<any[]> {
getPluginFiles(assign: AddonModAssignAssign, submission: AddonModAssignSubmission,
plugin: AddonModAssignPlugin, siteId?: string): Promise<any[]> {
return Promise.resolve(this.executeFunctionOnEnabled(plugin.type, 'getPluginFiles', [assign, submission, plugin, siteId]));
}
@ -208,7 +217,7 @@ export class AddonModAssignFeedbackDelegate extends CoreDelegate {
* @param plugin Plugin to get the name for.
* @return Human readable name.
*/
getPluginName(plugin: any): string {
getPluginName(plugin: AddonModAssignPlugin): string {
return this.executeFunctionOnEnabled(plugin.type, 'getPluginName', [plugin]);
}
@ -222,7 +231,8 @@ export class AddonModAssignFeedbackDelegate extends CoreDelegate {
* @param userId User ID of the submission.
* @return Promise resolved with true if data has changed, resolved with false otherwise.
*/
hasPluginDataChanged(assign: any, submission: any, plugin: any, inputData: any, userId: number): Promise<boolean> {
hasPluginDataChanged(assign: AddonModAssignAssign, submission: AddonModAssignSubmission,
plugin: AddonModAssignPlugin, inputData: any, userId: number): Promise<boolean> {
return Promise.resolve(this.executeFunctionOnEnabled(plugin.type, 'hasDataChanged',
[assign, submission, plugin, inputData, userId]));
}
@ -236,7 +246,8 @@ export class AddonModAssignFeedbackDelegate extends CoreDelegate {
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved with true if it has draft data.
*/
hasPluginDraftData(assignId: number, userId: number, plugin: any, siteId?: string): Promise<boolean> {
hasPluginDraftData(assignId: number, userId: number, plugin: AddonModAssignPlugin, siteId?: string)
: Promise<boolean> {
return Promise.resolve(this.executeFunctionOnEnabled(plugin.type, 'hasDraftData', [assignId, userId, siteId]));
}
@ -259,7 +270,8 @@ export class AddonModAssignFeedbackDelegate extends CoreDelegate {
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved when done.
*/
prefetch(assign: any, submission: any, plugin: any, siteId?: string): Promise<any> {
prefetch(assign: AddonModAssignAssign, submission: AddonModAssignSubmission, plugin: AddonModAssignPlugin,
siteId?: string): Promise<any> {
return Promise.resolve(this.executeFunctionOnEnabled(plugin.type, 'prefetch', [assign, submission, plugin, siteId]));
}
@ -273,7 +285,8 @@ export class AddonModAssignFeedbackDelegate extends CoreDelegate {
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved when data has been gathered.
*/
preparePluginFeedbackData(assignId: number, userId: number, plugin: any, pluginData: any, siteId?: string): Promise<any> {
preparePluginFeedbackData(assignId: number, userId: number, plugin: AddonModAssignPlugin, pluginData: any,
siteId?: string): Promise<any> {
return Promise.resolve(this.executeFunctionOnEnabled(plugin.type, 'prepareFeedbackData',
[assignId, userId, plugin, pluginData, siteId]));
@ -289,7 +302,8 @@ export class AddonModAssignFeedbackDelegate extends CoreDelegate {
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved when data has been saved.
*/
saveFeedbackDraft(assignId: number, userId: number, plugin: any, inputData: any, siteId?: string): Promise<any> {
saveFeedbackDraft(assignId: number, userId: number, plugin: AddonModAssignPlugin, inputData: any,
siteId?: string): Promise<any> {
return Promise.resolve(this.executeFunctionOnEnabled(plugin.type, 'saveDraft',
[assignId, userId, plugin, inputData, siteId]));
}

View File

@ -21,7 +21,10 @@ import { CoreUtilsProvider } from '@providers/utils/utils';
import { CoreFileUploaderProvider } from '@core/fileuploader/providers/fileuploader';
import { AddonModAssignFeedbackDelegate } from './feedback-delegate';
import { AddonModAssignSubmissionDelegate } from './submission-delegate';
import { AddonModAssignProvider } from './assign';
import {
AddonModAssignProvider, AddonModAssignAssign, AddonModAssignSubmission, AddonModAssignParticipant,
AddonModAssignSubmissionFeedback
} from './assign';
import { AddonModAssignOfflineProvider } from './assign-offline';
/**
@ -46,7 +49,7 @@ export class AddonModAssignHelperProvider {
* @param submission Submission.
* @return Whether it can be edited offline.
*/
canEditSubmissionOffline(assign: any, submission: any): Promise<boolean> {
canEditSubmissionOffline(assign: AddonModAssignAssign, submission: AddonModAssignSubmission): Promise<boolean> {
if (!submission) {
return Promise.resolve(false);
}
@ -81,7 +84,7 @@ export class AddonModAssignHelperProvider {
* @param submission Submission to clear the data for.
* @param inputData Data entered in the submission form.
*/
clearSubmissionPluginTmpData(assign: any, submission: any, inputData: any): void {
clearSubmissionPluginTmpData(assign: AddonModAssignAssign, submission: AddonModAssignSubmission, inputData: any): void {
submission.plugins.forEach((plugin) => {
this.submissionDelegate.clearTmpData(assign, submission, plugin, inputData);
});
@ -95,7 +98,7 @@ export class AddonModAssignHelperProvider {
* @param previousSubmission Submission to copy.
* @return Promise resolved when done.
*/
copyPreviousAttempt(assign: any, previousSubmission: any): Promise<any> {
copyPreviousAttempt(assign: AddonModAssignAssign, previousSubmission: AddonModAssignSubmission): Promise<any> {
const pluginData = {},
promises = [];
@ -112,6 +115,36 @@ export class AddonModAssignHelperProvider {
});
}
/**
* Create an empty feedback object.
*
* @return Feedback.
*/
createEmptyFeedback(): AddonModAssignSubmissionFeedback {
return {
grade: undefined,
gradefordisplay: undefined,
gradeddate: undefined
};
}
/**
* Create an empty submission object.
*
* @return Submission.
*/
createEmptySubmission(): AddonModAssignSubmissionFormatted {
return {
id: undefined,
userid: undefined,
attemptnumber: undefined,
timecreated: undefined,
timemodified: undefined,
status: undefined,
groupid: undefined
};
}
/**
* Delete stored submission files for a plugin. See storeSubmissionFiles.
*
@ -136,7 +169,9 @@ export class AddonModAssignHelperProvider {
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved when done.
*/
discardFeedbackPluginData(assignId: number, userId: number, feedback: any, siteId?: string): Promise<any> {
discardFeedbackPluginData(assignId: number, userId: number, feedback: AddonModAssignSubmissionFeedback,
siteId?: string): Promise<any> {
const promises = [];
feedback.plugins.forEach((plugin) => {
@ -155,7 +190,9 @@ export class AddonModAssignHelperProvider {
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved with the list of participants and summary of submissions.
*/
getParticipants(assign: any, groupId?: number, ignoreCache?: boolean, siteId?: string): Promise<any[]> {
getParticipants(assign: AddonModAssignAssign, groupId?: number, ignoreCache?: boolean, siteId?: string)
: Promise<AddonModAssignParticipant[]> {
groupId = groupId || 0;
siteId = siteId || this.sitesProvider.getCurrentSiteId();
@ -167,7 +204,7 @@ export class AddonModAssignHelperProvider {
// If no participants returned and all groups specified, get participants by groups.
return this.groupsProvider.getActivityGroupInfo(assign.cmid, false, undefined, siteId).then((info) => {
const promises = [],
participants = {};
participants: {[id: number]: AddonModAssignParticipant} = {};
info.groups.forEach((userGroup) => {
promises.push(this.assignProvider.listParticipants(assign.id, userGroup.id, ignoreCache, siteId)
@ -194,8 +231,8 @@ export class AddonModAssignHelperProvider {
* @param type Name of the subplugin.
* @return Object containing all configurations of the subplugin selected.
*/
getPluginConfig(assign: any, subtype: string, type: string): any {
const configs = {};
getPluginConfig(assign: AddonModAssignAssign, subtype: string, type: string): {[name: string]: string} {
const configs: {[name: string]: string} = {};
assign.configs.forEach((config) => {
if (config.subtype == subtype && config.plugin == type) {
@ -213,7 +250,7 @@ export class AddonModAssignHelperProvider {
* @param subtype Subtype name (assignsubmission or assignfeedback)
* @return List of enabled plugins for the assign.
*/
getPluginsEnabled(assign: any, subtype: string): any[] {
getPluginsEnabled(assign: AddonModAssignAssign, subtype: string): any[] {
const enabled = [];
assign.configs.forEach((config) => {
@ -250,7 +287,7 @@ export class AddonModAssignHelperProvider {
* @param previousSubmission Submission to copy.
* @return Promise resolved with the size.
*/
getSubmissionSizeForCopy(assign: any, previousSubmission: any): Promise<number> {
getSubmissionSizeForCopy(assign: AddonModAssignAssign, previousSubmission: AddonModAssignSubmission): Promise<number> {
const promises = [];
let totalSize = 0;
@ -273,7 +310,8 @@ export class AddonModAssignHelperProvider {
* @param inputData Data entered in the submission form.
* @return Promise resolved with the size.
*/
getSubmissionSizeForEdit(assign: any, submission: any, inputData: any): Promise<number> {
getSubmissionSizeForEdit(assign: AddonModAssignAssign, submission: AddonModAssignSubmission, inputData: any): Promise<number> {
const promises = [];
let totalSize = 0;
@ -298,14 +336,14 @@ export class AddonModAssignHelperProvider {
* @param siteId Site id (empty for current site).
* @return Promise always resolved. Resolve param is the formatted submissions.
*/
getSubmissionsUserData(assign: any, submissions: any[], groupId?: number, ignoreCache?: boolean, siteId?: string):
Promise<any[]> {
return this.getParticipants(assign, groupId).then((participants) => {
getSubmissionsUserData(assign: AddonModAssignAssign, submissions: AddonModAssignSubmissionFormatted[], groupId?: number,
ignoreCache?: boolean, siteId?: string): Promise<AddonModAssignSubmissionFormatted[]> {
return this.getParticipants(assign, groupId).then((parts) => {
const blind = assign.blindmarking && !assign.revealidentities;
const promises = [];
const result = [];
participants = this.utils.arrayToObject(participants, 'id');
const result: AddonModAssignSubmissionFormatted[] = [];
const participants: {[id: number]: AddonModAssignParticipant} = this.utils.arrayToObject(parts, 'id');
submissions.forEach((submission) => {
submission.submitid = submission.userid > 0 ? submission.userid : submission.blindid;
@ -356,10 +394,10 @@ export class AddonModAssignHelperProvider {
return Promise.all(promises).then(() => {
// Create a submission for each participant left in the list (the participants already treated were removed).
this.utils.objectToArray(participants).forEach((participant) => {
const submission: any = {
submitid: participant.id
};
this.utils.objectToArray(participants).forEach((participant: AddonModAssignParticipant) => {
const submission = this.createEmptySubmission();
submission.submitid = participant.id;
if (!blind) {
submission.userid = participant.id;
@ -390,17 +428,20 @@ export class AddonModAssignHelperProvider {
* Check if the feedback data has changed for a certain submission and assign.
*
* @param assign Assignment.
* @param userId User Id.
* @param submission The submission.
* @param feedback Feedback data.
* @param userId The user ID.
* @return Promise resolved with true if data has changed, resolved with false otherwise.
*/
hasFeedbackDataChanged(assign: any, userId: number, feedback: any): Promise<boolean> {
hasFeedbackDataChanged(assign: AddonModAssignAssign, submission: AddonModAssignSubmission,
feedback: AddonModAssignSubmissionFeedback, userId: number): Promise<boolean> {
const promises = [];
let hasChanged = false;
feedback.plugins.forEach((plugin) => {
promises.push(this.prepareFeedbackPluginData(assign.id, userId, feedback).then((inputData) => {
return this.feedbackDelegate.hasPluginDataChanged(assign, userId, plugin, inputData, userId).then((changed) => {
return this.feedbackDelegate.hasPluginDataChanged(assign, submission, plugin, inputData, userId).then((changed) => {
if (changed) {
hasChanged = true;
}
@ -423,7 +464,9 @@ export class AddonModAssignHelperProvider {
* @param inputData Data entered in the submission form.
* @return Promise resolved with true if data has changed, resolved with false otherwise.
*/
hasSubmissionDataChanged(assign: any, submission: any, inputData: any): Promise<boolean> {
hasSubmissionDataChanged(assign: AddonModAssignAssign, submission: AddonModAssignSubmission, inputData: any)
: Promise<boolean> {
const promises = [];
let hasChanged = false;
@ -451,7 +494,9 @@ export class AddonModAssignHelperProvider {
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved with plugin data to send to server.
*/
prepareFeedbackPluginData(assignId: number, userId: number, feedback: any, siteId?: string): Promise<any> {
prepareFeedbackPluginData(assignId: number, userId: number, feedback: AddonModAssignSubmissionFeedback, siteId?: string)
: Promise<any> {
const pluginData = {},
promises = [];
@ -473,7 +518,9 @@ export class AddonModAssignHelperProvider {
* @param offline True to prepare the data for an offline submission, false otherwise.
* @return Promise resolved with plugin data to send to server.
*/
prepareSubmissionPluginData(assign: any, submission: any, inputData: any, offline?: boolean): Promise<any> {
prepareSubmissionPluginData(assign: AddonModAssignAssign, submission: AddonModAssignSubmission, inputData: any,
offline?: boolean): Promise<any> {
const pluginData = {},
promises = [];
@ -553,3 +600,16 @@ export class AddonModAssignHelperProvider {
}
}
}
/**
* Assign submission with some calculated data.
*/
export type AddonModAssignSubmissionFormatted = AddonModAssignSubmission & {
blindid?: number; // Calculated in the app. Blindid of the user that did the submission.
submitid?: number; // Calculated in the app. Userid or blindid of the user that did the submission.
userfullname?: string; // Calculated in the app. Full name of the user that did the submission.
userprofileimageurl?: string; // Calculated in the app. Avatar of the user that did the submission.
manyGroups?: boolean; // Calculated in the app. Whether the user belongs to more than 1 group.
noGroups?: boolean; // Calculated in the app. Whether the user doesn't belong to any group.
groupname?: string; // Calculated in the app. Name of the group the submission belongs to.
};

View File

@ -26,8 +26,8 @@ import { CoreCourseProvider } from '@core/course/providers/course';
import { CoreCourseHelperProvider } from '@core/course/providers/helper';
import { CoreGradesHelperProvider } from '@core/grades/providers/helper';
import { CoreUserProvider } from '@core/user/providers/user';
import { AddonModAssignProvider } from './assign';
import { AddonModAssignHelperProvider } from './helper';
import { AddonModAssignProvider, AddonModAssignGetSubmissionStatusResult, AddonModAssignSubmission } from './assign';
import { AddonModAssignHelperProvider, AddonModAssignSubmissionFormatted } from './helper';
import { AddonModAssignSyncProvider } from './assign-sync';
import { AddonModAssignFeedbackDelegate } from './feedback-delegate';
import { AddonModAssignSubmissionDelegate } from './submission-delegate';
@ -106,7 +106,7 @@ export class AddonModAssignPrefetchHandler extends CoreCourseActivityPrefetchHan
if (data.canviewsubmissions) {
// Teacher, get all submissions.
return this.assignHelper.getSubmissionsUserData(assign, data.submissions, 0, false, siteId)
.then((submissions) => {
.then((submissions: AddonModAssignSubmissionFormatted[]) => {
const promises = [];
@ -161,9 +161,10 @@ export class AddonModAssignPrefetchHandler extends CoreCourseActivityPrefetchHan
return this.assignProvider.getSubmissionStatusWithRetry(assign, submitId, undefined, blindMarking, true, false, siteId)
.then((response) => {
const promises = [];
let userSubmission: AddonModAssignSubmission;
if (response.lastattempt) {
const userSubmission = this.assignProvider.getSubmissionObjectFromAttempt(assign, response.lastattempt);
userSubmission = this.assignProvider.getSubmissionObjectFromAttempt(assign, response.lastattempt);
if (userSubmission && userSubmission.plugins) {
// Add submission plugin files.
userSubmission.plugins.forEach((plugin) => {
@ -175,7 +176,7 @@ export class AddonModAssignPrefetchHandler extends CoreCourseActivityPrefetchHan
if (response.feedback && response.feedback.plugins) {
// Add feedback plugin files.
response.feedback.plugins.forEach((plugin) => {
promises.push(this.feedbackDelegate.getPluginFiles(assign, response, plugin, siteId));
promises.push(this.feedbackDelegate.getPluginFiles(assign, userSubmission, plugin, siteId));
});
}
@ -303,7 +304,7 @@ export class AddonModAssignPrefetchHandler extends CoreCourseActivityPrefetchHan
groupInfo.groups.forEach((group) => {
groupProms.push(this.assignHelper.getSubmissionsUserData(assign, data.submissions, group.id, true, siteId)
.then((submissions) => {
.then((submissions: AddonModAssignSubmissionFormatted[]) => {
const subPromises = [];
@ -327,7 +328,8 @@ export class AddonModAssignPrefetchHandler extends CoreCourseActivityPrefetchHan
}
// Prefetch the submission of the current user even if it does not exist, this will be create it.
if (!data.submissions || !data.submissions.find((subm) => subm.submitid == userId)) {
if (!data.submissions ||
!data.submissions.find((subm: AddonModAssignSubmissionFormatted) => subm.submitid == userId)) {
subPromises.push(this.assignProvider.getSubmissionStatusWithRetry(assign, userId, group.id,
false, true, true, siteId).then((subm) => {
return this.prefetchSubmission(assign, courseId, moduleId, subm, userId, siteId);
@ -385,15 +387,16 @@ export class AddonModAssignPrefetchHandler extends CoreCourseActivityPrefetchHan
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved when prefetched, rejected otherwise.
*/
protected prefetchSubmission(assign: any, courseId: number, moduleId: number, submission: any, userId?: number,
siteId?: string): Promise<any> {
protected prefetchSubmission(assign: any, courseId: number, moduleId: number,
submission: AddonModAssignGetSubmissionStatusResult, userId?: number, siteId?: string): Promise<any> {
const promises = [],
blindMarking = assign.blindmarking && !assign.revealidentities;
let userIds = [];
let userIds = [],
userSubmission: AddonModAssignSubmission;
if (submission.lastattempt) {
const userSubmission = this.assignProvider.getSubmissionObjectFromAttempt(assign, submission.lastattempt);
userSubmission = this.assignProvider.getSubmissionObjectFromAttempt(assign, submission.lastattempt);
// Get IDs of the members who need to submit.
if (!blindMarking && submission.lastattempt.submissiongroupmemberswhoneedtosubmit) {
@ -440,10 +443,10 @@ export class AddonModAssignPrefetchHandler extends CoreCourseActivityPrefetchHan
if (submission.feedback.plugins) {
submission.feedback.plugins.forEach((plugin) => {
// Prefetch the plugin WS data.
promises.push(this.feedbackDelegate.prefetch(assign, submission, plugin, siteId));
promises.push(this.feedbackDelegate.prefetch(assign, userSubmission, plugin, siteId));
// Prefetch the plugin files.
promises.push(this.feedbackDelegate.getPluginFiles(assign, submission, plugin, siteId).then((files) => {
promises.push(this.feedbackDelegate.getPluginFiles(assign, userSubmission, plugin, siteId).then((files) => {
return this.filepoolProvider.addFilesToQueue(siteId, files, this.component, module.id);
}).catch(() => {
// Ignore errors.

View File

@ -18,6 +18,7 @@ import { CoreEventsProvider } from '@providers/events';
import { CoreSitesProvider } from '@providers/sites';
import { CoreDelegate, CoreDelegateHandler } from '@classes/delegate';
import { AddonModAssignDefaultSubmissionHandler } from './default-submission-handler';
import { AddonModAssignAssign, AddonModAssignSubmission, AddonModAssignPlugin } from './assign';
/**
* Interface that all submission handlers must implement.
@ -39,7 +40,8 @@ export interface AddonModAssignSubmissionHandler extends CoreDelegateHandler {
* @param plugin The plugin object.
* @return Boolean or promise resolved with boolean: whether it can be edited in offline.
*/
canEditOffline?(assign: any, submission: any, plugin: any): boolean | Promise<boolean>;
canEditOffline?(assign: AddonModAssignAssign, submission: AddonModAssignSubmission,
plugin: AddonModAssignPlugin): boolean | Promise<boolean>;
/**
* Should clear temporary data for a cancelled submission.
@ -49,7 +51,8 @@ export interface AddonModAssignSubmissionHandler extends CoreDelegateHandler {
* @param plugin The plugin object.
* @param inputData Data entered by the user for the submission.
*/
clearTmpData?(assign: any, submission: any, plugin: any, inputData: any): void;
clearTmpData?(assign: AddonModAssignAssign, submission: AddonModAssignSubmission,
plugin: AddonModAssignPlugin, inputData: any): void;
/**
* This function will be called when the user wants to create a new submission based on the previous one.
@ -62,7 +65,8 @@ export interface AddonModAssignSubmissionHandler extends CoreDelegateHandler {
* @param siteId Site ID. If not defined, current site.
* @return If the function is async, it should return a Promise resolved when done.
*/
copySubmissionData?(assign: any, plugin: any, pluginData: any, userId?: number, siteId?: string): void | Promise<any>;
copySubmissionData?(assign: AddonModAssignAssign, plugin: AddonModAssignPlugin, pluginData: any,
userId?: number, siteId?: string): void | Promise<any>;
/**
* Delete any stored data for the plugin and submission.
@ -74,7 +78,8 @@ export interface AddonModAssignSubmissionHandler extends CoreDelegateHandler {
* @param siteId Site ID. If not defined, current site.
* @return If the function is async, it should return a Promise resolved when done.
*/
deleteOfflineData?(assign: any, submission: any, plugin: any, offlineData: any, siteId?: string): void | Promise<any>;
deleteOfflineData?(assign: AddonModAssignAssign, submission: AddonModAssignSubmission,
plugin: AddonModAssignPlugin, offlineData: any, siteId?: string): void | Promise<any>;
/**
* Return the Component to use to display the plugin data, either in read or in edit mode.
@ -85,7 +90,7 @@ export interface AddonModAssignSubmissionHandler extends CoreDelegateHandler {
* @param edit Whether the user is editing.
* @return The component (or promise resolved with component) to use, undefined if not found.
*/
getComponent?(injector: Injector, plugin: any, edit?: boolean): any | Promise<any>;
getComponent?(injector: Injector, plugin: AddonModAssignPlugin, edit?: boolean): any | Promise<any>;
/**
* Get files used by this plugin.
@ -97,7 +102,8 @@ export interface AddonModAssignSubmissionHandler extends CoreDelegateHandler {
* @param siteId Site ID. If not defined, current site.
* @return The files (or promise resolved with the files).
*/
getPluginFiles?(assign: any, submission: any, plugin: any, siteId?: string): any[] | Promise<any[]>;
getPluginFiles?(assign: AddonModAssignAssign, submission: AddonModAssignSubmission,
plugin: AddonModAssignPlugin, siteId?: string): any[] | Promise<any[]>;
/**
* Get a readable name to use for the plugin.
@ -105,7 +111,7 @@ export interface AddonModAssignSubmissionHandler extends CoreDelegateHandler {
* @param plugin The plugin object.
* @return The plugin name.
*/
getPluginName?(plugin: any): string;
getPluginName?(plugin: AddonModAssignPlugin): string;
/**
* Get the size of data (in bytes) this plugin will send to copy a previous submission.
@ -114,7 +120,7 @@ export interface AddonModAssignSubmissionHandler extends CoreDelegateHandler {
* @param plugin The plugin object.
* @return The size (or promise resolved with size).
*/
getSizeForCopy?(assign: any, plugin: any): number | Promise<number>;
getSizeForCopy?(assign: AddonModAssignAssign, plugin: AddonModAssignPlugin): number | Promise<number>;
/**
* Get the size of data (in bytes) this plugin will send to add or edit a submission.
@ -125,7 +131,8 @@ export interface AddonModAssignSubmissionHandler extends CoreDelegateHandler {
* @param inputData Data entered by the user for the submission.
* @return The size (or promise resolved with size).
*/
getSizeForEdit?(assign: any, submission: any, plugin: any, inputData: any): number | Promise<number>;
getSizeForEdit?(assign: AddonModAssignAssign, submission: AddonModAssignSubmission,
plugin: AddonModAssignPlugin, inputData: any): number | Promise<number>;
/**
* Check if the submission data has changed for this plugin.
@ -136,7 +143,8 @@ export interface AddonModAssignSubmissionHandler extends CoreDelegateHandler {
* @param inputData Data entered by the user for the submission.
* @return Boolean (or promise resolved with boolean): whether the data has changed.
*/
hasDataChanged?(assign: any, submission: any, plugin: any, inputData: any): boolean | Promise<boolean>;
hasDataChanged?(assign: AddonModAssignAssign, submission: AddonModAssignSubmission,
plugin: AddonModAssignPlugin, inputData: any): boolean | Promise<boolean>;
/**
* Whether or not the handler is enabled for edit on a site level.
@ -155,7 +163,8 @@ export interface AddonModAssignSubmissionHandler extends CoreDelegateHandler {
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved when done.
*/
prefetch?(assign: any, submission: any, plugin: any, siteId?: string): Promise<any>;
prefetch?(assign: AddonModAssignAssign, submission: AddonModAssignSubmission,
plugin: AddonModAssignPlugin, siteId?: string): Promise<any>;
/**
* Prepare and add to pluginData the data to send to the server based on the input data.
@ -170,8 +179,9 @@ export interface AddonModAssignSubmissionHandler extends CoreDelegateHandler {
* @param siteId Site ID. If not defined, current site.
* @return If the function is async, it should return a Promise resolved when done.
*/
prepareSubmissionData?(assign: any, submission: any, plugin: any, inputData: any, pluginData: any, offline?: boolean,
userId?: number, siteId?: string): void | Promise<any>;
prepareSubmissionData?(assign: AddonModAssignAssign, submission: AddonModAssignSubmission,
plugin: AddonModAssignPlugin, inputData: any, pluginData: any, offline?: boolean,
userId?: number, siteId?: string): void | Promise<any>;
/**
* Prepare and add to pluginData the data to send to the server based on the offline data stored.
@ -185,8 +195,8 @@ export interface AddonModAssignSubmissionHandler extends CoreDelegateHandler {
* @param siteId Site ID. If not defined, current site.
* @return If the function is async, it should return a Promise resolved when done.
*/
prepareSyncData?(assign: any, submission: any, plugin: any, offlineData: any, pluginData: any, siteId?: string)
: void | Promise<any>;
prepareSyncData?(assign: AddonModAssignAssign, submission: AddonModAssignSubmission,
plugin: AddonModAssignPlugin, offlineData: any, pluginData: any, siteId?: string): void | Promise<any>;
}
/**
@ -210,7 +220,8 @@ export class AddonModAssignSubmissionDelegate extends CoreDelegate {
* @param plugin The plugin object.
* @return Promise resolved with boolean: whether it can be edited in offline.
*/
canPluginEditOffline(assign: any, submission: any, plugin: any): Promise<boolean> {
canPluginEditOffline(assign: AddonModAssignAssign, submission: AddonModAssignSubmission,
plugin: AddonModAssignPlugin): Promise<boolean> {
return Promise.resolve(this.executeFunctionOnEnabled(plugin.type, 'canEditOffline', [assign, submission, plugin]));
}
@ -222,7 +233,8 @@ export class AddonModAssignSubmissionDelegate extends CoreDelegate {
* @param plugin The plugin object.
* @param inputData Data entered by the user for the submission.
*/
clearTmpData(assign: any, submission: any, plugin: any, inputData: any): void {
clearTmpData(assign: AddonModAssignAssign, submission: AddonModAssignSubmission,
plugin: AddonModAssignPlugin, inputData: any): void {
return this.executeFunctionOnEnabled(plugin.type, 'clearTmpData', [assign, submission, plugin, inputData]);
}
@ -236,7 +248,8 @@ export class AddonModAssignSubmissionDelegate extends CoreDelegate {
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved when the data has been copied.
*/
copyPluginSubmissionData(assign: any, plugin: any, pluginData: any, userId?: number, siteId?: string): void | Promise<any> {
copyPluginSubmissionData(assign: AddonModAssignAssign, plugin: AddonModAssignPlugin, pluginData: any,
userId?: number, siteId?: string): void | Promise<any> {
return Promise.resolve(this.executeFunctionOnEnabled(plugin.type, 'copySubmissionData',
[assign, plugin, pluginData, userId, siteId]));
}
@ -251,7 +264,8 @@ export class AddonModAssignSubmissionDelegate extends CoreDelegate {
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved when done.
*/
deletePluginOfflineData(assign: any, submission: any, plugin: any, offlineData: any, siteId?: string): Promise<any> {
deletePluginOfflineData(assign: AddonModAssignAssign, submission: AddonModAssignSubmission,
plugin: AddonModAssignPlugin, offlineData: any, siteId?: string): Promise<any> {
return Promise.resolve(this.executeFunctionOnEnabled(plugin.type, 'deleteOfflineData',
[assign, submission, plugin, offlineData, siteId]));
}
@ -264,7 +278,7 @@ export class AddonModAssignSubmissionDelegate extends CoreDelegate {
* @param edit Whether the user is editing.
* @return Promise resolved with the component to use, undefined if not found.
*/
getComponentForPlugin(injector: Injector, plugin: any, edit?: boolean): Promise<any> {
getComponentForPlugin(injector: Injector, plugin: AddonModAssignPlugin, edit?: boolean): Promise<any> {
return Promise.resolve(this.executeFunctionOnEnabled(plugin.type, 'getComponent', [injector, plugin, edit]));
}
@ -278,7 +292,8 @@ export class AddonModAssignSubmissionDelegate extends CoreDelegate {
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved with the files.
*/
getPluginFiles(assign: any, submission: any, plugin: any, siteId?: string): Promise<any[]> {
getPluginFiles(assign: AddonModAssignAssign, submission: AddonModAssignSubmission,
plugin: AddonModAssignPlugin, siteId?: string): Promise<any[]> {
return Promise.resolve(this.executeFunctionOnEnabled(plugin.type, 'getPluginFiles', [assign, submission, plugin, siteId]));
}
@ -288,7 +303,7 @@ export class AddonModAssignSubmissionDelegate extends CoreDelegate {
* @param plugin Plugin to get the name for.
* @return Human readable name.
*/
getPluginName(plugin: any): string {
getPluginName(plugin: AddonModAssignPlugin): string {
return this.executeFunctionOnEnabled(plugin.type, 'getPluginName', [plugin]);
}
@ -299,7 +314,7 @@ export class AddonModAssignSubmissionDelegate extends CoreDelegate {
* @param plugin The plugin object.
* @return Promise resolved with size.
*/
getPluginSizeForCopy(assign: any, plugin: any): Promise<number> {
getPluginSizeForCopy(assign: AddonModAssignAssign, plugin: AddonModAssignPlugin): Promise<number> {
return Promise.resolve(this.executeFunctionOnEnabled(plugin.type, 'getSizeForCopy', [assign, plugin]));
}
@ -312,7 +327,8 @@ export class AddonModAssignSubmissionDelegate extends CoreDelegate {
* @param inputData Data entered by the user for the submission.
* @return Promise resolved with size.
*/
getPluginSizeForEdit(assign: any, submission: any, plugin: any, inputData: any): Promise<number> {
getPluginSizeForEdit(assign: AddonModAssignAssign, submission: AddonModAssignSubmission,
plugin: AddonModAssignPlugin, inputData: any): Promise<number> {
return Promise.resolve(this.executeFunctionOnEnabled(plugin.type, 'getSizeForEdit',
[assign, submission, plugin, inputData]));
}
@ -326,7 +342,8 @@ export class AddonModAssignSubmissionDelegate extends CoreDelegate {
* @param inputData Data entered by the user for the submission.
* @return Promise resolved with true if data has changed, resolved with false otherwise.
*/
hasPluginDataChanged(assign: any, submission: any, plugin: any, inputData: any): Promise<boolean> {
hasPluginDataChanged(assign: AddonModAssignAssign, submission: AddonModAssignSubmission,
plugin: AddonModAssignPlugin, inputData: any): Promise<boolean> {
return Promise.resolve(this.executeFunctionOnEnabled(plugin.type, 'hasDataChanged',
[assign, submission, plugin, inputData]));
}
@ -360,7 +377,8 @@ export class AddonModAssignSubmissionDelegate extends CoreDelegate {
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved when done.
*/
prefetch(assign: any, submission: any, plugin: any, siteId?: string): Promise<any> {
prefetch(assign: AddonModAssignAssign, submission: AddonModAssignSubmission, plugin: AddonModAssignPlugin,
siteId?: string): Promise<any> {
return Promise.resolve(this.executeFunctionOnEnabled(plugin.type, 'prefetch', [assign, submission, plugin, siteId]));
}
@ -377,8 +395,9 @@ export class AddonModAssignSubmissionDelegate extends CoreDelegate {
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved when data has been gathered.
*/
preparePluginSubmissionData(assign: any, submission: any, plugin: any, inputData: any, pluginData: any, offline?: boolean,
userId?: number, siteId?: string): Promise<any> {
preparePluginSubmissionData(assign: AddonModAssignAssign, submission: AddonModAssignSubmission,
plugin: AddonModAssignPlugin, inputData: any, pluginData: any, offline?: boolean, userId?: number,
siteId?: string): Promise<any> {
return Promise.resolve(this.executeFunctionOnEnabled(plugin.type, 'prepareSubmissionData',
[assign, submission, plugin, inputData, pluginData, offline, userId, siteId]));
@ -395,8 +414,8 @@ export class AddonModAssignSubmissionDelegate extends CoreDelegate {
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved when data has been gathered.
*/
preparePluginSyncData(assign: any, submission: any, plugin: any, offlineData: any, pluginData: any, siteId?: string)
: Promise<any> {
preparePluginSyncData(assign: AddonModAssignAssign, submission: AddonModAssignSubmission,
plugin: AddonModAssignPlugin, offlineData: any, pluginData: any, siteId?: string): Promise<any> {
return Promise.resolve(this.executeFunctionOnEnabled(plugin.type, 'prepareSyncData',
[assign, submission, plugin, offlineData, pluginData, siteId]));

View File

@ -17,6 +17,9 @@ import { Injectable, Injector } from '@angular/core';
import { CoreCommentsProvider } from '@core/comments/providers/comments';
import { AddonModAssignSubmissionHandler } from '../../../providers/submission-delegate';
import { AddonModAssignSubmissionCommentsComponent } from '../component/comments';
import {
AddonModAssignAssign, AddonModAssignSubmission, AddonModAssignPlugin
} from '../../../providers/assign';
/**
* Handler for comments submission plugin.
@ -38,7 +41,8 @@ export class AddonModAssignSubmissionCommentsHandler implements AddonModAssignSu
* @param plugin The plugin object.
* @return Boolean or promise resolved with boolean: whether it can be edited in offline.
*/
canEditOffline(assign: any, submission: any, plugin: any): boolean | Promise<boolean> {
canEditOffline(assign: AddonModAssignAssign, submission: AddonModAssignSubmission,
plugin: AddonModAssignPlugin): boolean | Promise<boolean> {
// This plugin is read only, but return true to prevent blocking the edition.
return true;
}
@ -52,7 +56,7 @@ export class AddonModAssignSubmissionCommentsHandler implements AddonModAssignSu
* @param edit Whether the user is editing.
* @return The component (or promise resolved with component) to use, undefined if not found.
*/
getComponent(injector: Injector, plugin: any, edit?: boolean): any | Promise<any> {
getComponent(injector: Injector, plugin: AddonModAssignPlugin, edit?: boolean): any | Promise<any> {
return edit ? undefined : AddonModAssignSubmissionCommentsComponent;
}
@ -84,7 +88,9 @@ export class AddonModAssignSubmissionCommentsHandler implements AddonModAssignSu
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved when done.
*/
prefetch(assign: any, submission: any, plugin: any, siteId?: string): Promise<any> {
prefetch(assign: AddonModAssignAssign, submission: AddonModAssignSubmission,
plugin: AddonModAssignPlugin, siteId?: string): Promise<any> {
return this.commentsProvider.getComments('module', assign.cmid, 'assignsubmission_comments', submission.id,
'submission_comments', 0, siteId).catch(() => {
// Fail silently (Moodle < 3.1.1, 3.2)

View File

@ -21,7 +21,9 @@ import { CoreSitesProvider } from '@providers/sites';
import { CoreWSProvider } from '@providers/ws';
import { CoreUtilsProvider } from '@providers/utils/utils';
import { CoreFileUploaderProvider } from '@core/fileuploader/providers/fileuploader';
import { AddonModAssignProvider } from '../../../providers/assign';
import {
AddonModAssignProvider, AddonModAssignAssign, AddonModAssignSubmission, AddonModAssignPlugin
} from '../../../providers/assign';
import { AddonModAssignOfflineProvider } from '../../../providers/assign-offline';
import { AddonModAssignHelperProvider } from '../../../providers/helper';
import { AddonModAssignSubmissionHandler } from '../../../providers/submission-delegate';
@ -53,7 +55,8 @@ export class AddonModAssignSubmissionFileHandler implements AddonModAssignSubmis
* @param plugin The plugin object.
* @return Boolean or promise resolved with boolean: whether it can be edited in offline.
*/
canEditOffline(assign: any, submission: any, plugin: any): boolean | Promise<boolean> {
canEditOffline(assign: AddonModAssignAssign, submission: AddonModAssignSubmission,
plugin: AddonModAssignPlugin): boolean | Promise<boolean> {
// This plugin doesn't use Moodle filters, it can be edited in offline.
return true;
}
@ -66,7 +69,8 @@ export class AddonModAssignSubmissionFileHandler implements AddonModAssignSubmis
* @param plugin The plugin object.
* @param inputData Data entered by the user for the submission.
*/
clearTmpData(assign: any, submission: any, plugin: any, inputData: any): void {
clearTmpData(assign: AddonModAssignAssign, submission: AddonModAssignSubmission,
plugin: AddonModAssignPlugin, inputData: any): void {
const files = this.fileSessionProvider.getFiles(AddonModAssignProvider.COMPONENT, assign.id);
// Clear the files in session for this assign.
@ -87,7 +91,9 @@ export class AddonModAssignSubmissionFileHandler implements AddonModAssignSubmis
* @param siteId Site ID. If not defined, current site.
* @return If the function is async, it should return a Promise resolved when done.
*/
copySubmissionData(assign: any, plugin: any, pluginData: any, userId?: number, siteId?: string): void | Promise<any> {
copySubmissionData(assign: AddonModAssignAssign, plugin: AddonModAssignPlugin, pluginData: any,
userId?: number, siteId?: string): void | Promise<any> {
// We need to re-upload all the existing files.
const files = this.assignProvider.getSubmissionPluginAttachments(plugin);
@ -105,7 +111,7 @@ export class AddonModAssignSubmissionFileHandler implements AddonModAssignSubmis
* @param edit Whether the user is editing.
* @return The component (or promise resolved with component) to use, undefined if not found.
*/
getComponent(injector: Injector, plugin: any, edit?: boolean): any | Promise<any> {
getComponent(injector: Injector, plugin: AddonModAssignPlugin, edit?: boolean): any | Promise<any> {
return AddonModAssignSubmissionFileComponent;
}
@ -119,7 +125,9 @@ export class AddonModAssignSubmissionFileHandler implements AddonModAssignSubmis
* @param siteId Site ID. If not defined, current site.
* @return If the function is async, it should return a Promise resolved when done.
*/
deleteOfflineData(assign: any, submission: any, plugin: any, offlineData: any, siteId?: string): void | Promise<any> {
deleteOfflineData(assign: AddonModAssignAssign, submission: AddonModAssignSubmission,
plugin: AddonModAssignPlugin, offlineData: any, siteId?: string): void | Promise<any> {
return this.assignHelper.deleteStoredSubmissionFiles(assign.id, AddonModAssignSubmissionFileHandler.FOLDER_NAME,
submission.userid, siteId).catch(() => {
// Ignore errors, maybe the folder doesn't exist.
@ -136,7 +144,8 @@ export class AddonModAssignSubmissionFileHandler implements AddonModAssignSubmis
* @param siteId Site ID. If not defined, current site.
* @return The files (or promise resolved with the files).
*/
getPluginFiles(assign: any, submission: any, plugin: any, siteId?: string): any[] | Promise<any[]> {
getPluginFiles(assign: AddonModAssignAssign, submission: AddonModAssignSubmission,
plugin: AddonModAssignPlugin, siteId?: string): any[] | Promise<any[]> {
return this.assignProvider.getSubmissionPluginAttachments(plugin);
}
@ -147,7 +156,7 @@ export class AddonModAssignSubmissionFileHandler implements AddonModAssignSubmis
* @param plugin The plugin object.
* @return The size (or promise resolved with size).
*/
getSizeForCopy(assign: any, plugin: any): number | Promise<number> {
getSizeForCopy(assign: AddonModAssignAssign, plugin: AddonModAssignPlugin): number | Promise<number> {
const files = this.assignProvider.getSubmissionPluginAttachments(plugin),
promises = [];
let totalSize = 0;
@ -177,7 +186,8 @@ export class AddonModAssignSubmissionFileHandler implements AddonModAssignSubmis
* @param inputData Data entered by the user for the submission.
* @return The size (or promise resolved with size).
*/
getSizeForEdit(assign: any, submission: any, plugin: any, inputData: any): number | Promise<number> {
getSizeForEdit(assign: AddonModAssignAssign, submission: AddonModAssignSubmission,
plugin: AddonModAssignPlugin, inputData: any): number | Promise<number> {
const siteId = this.sitesProvider.getCurrentSiteId();
// Check if there's any change.
@ -232,7 +242,9 @@ export class AddonModAssignSubmissionFileHandler implements AddonModAssignSubmis
* @param inputData Data entered by the user for the submission.
* @return Boolean (or promise resolved with boolean): whether the data has changed.
*/
hasDataChanged(assign: any, submission: any, plugin: any, inputData: any): boolean | Promise<boolean> {
hasDataChanged(assign: AddonModAssignAssign, submission: AddonModAssignSubmission,
plugin: AddonModAssignPlugin, inputData: any): boolean | Promise<boolean> {
// Check if there's any offline data.
return this.assignOfflineProvider.getSubmission(assign.id, submission.userid).catch(() => {
// No offline data found.
@ -299,7 +311,8 @@ export class AddonModAssignSubmissionFileHandler implements AddonModAssignSubmis
* @param siteId Site ID. If not defined, current site.
* @return If the function is async, it should return a Promise resolved when done.
*/
prepareSubmissionData(assign: any, submission: any, plugin: any, inputData: any, pluginData: any, offline?: boolean,
prepareSubmissionData(assign: AddonModAssignAssign, submission: AddonModAssignSubmission,
plugin: AddonModAssignPlugin, inputData: any, pluginData: any, offline?: boolean,
userId?: number, siteId?: string): void | Promise<any> {
if (this.hasDataChanged(assign, submission, plugin, inputData)) {
@ -330,8 +343,8 @@ export class AddonModAssignSubmissionFileHandler implements AddonModAssignSubmis
* @param siteId Site ID. If not defined, current site.
* @return If the function is async, it should return a Promise resolved when done.
*/
prepareSyncData(assign: any, submission: any, plugin: any, offlineData: any, pluginData: any, siteId?: string)
: void | Promise<any> {
prepareSyncData(assign: AddonModAssignAssign, submission: AddonModAssignSubmission,
plugin: AddonModAssignPlugin, offlineData: any, pluginData: any, siteId?: string): void | Promise<any> {
const filesData = offlineData && offlineData.plugindata && offlineData.plugindata.files_filemanager;
if (filesData) {

View File

@ -10,7 +10,7 @@
<!-- Edit -->
<div *ngIf="edit && loaded">
<ion-item-divider text-wrap>{{ plugin.name }}</ion-item-divider>
<ion-item text-wrap *ngIf="configs.wordlimitenabled && words >= 0">
<ion-item text-wrap *ngIf="wordLimitEnabled && words >= 0">
<h2>{{ 'addon.mod_assign.wordlimit' | translate }}</h2>
<p>{{ 'core.numwords' | translate: {'$a': words + ' / ' + configs.wordlimit} }}</p>
</ion-item>

View File

@ -34,6 +34,7 @@ export class AddonModAssignSubmissionOnlineTextComponent extends AddonModAssignS
component = AddonModAssignProvider.COMPONENT;
text: string;
loaded: boolean;
wordLimitEnabled: boolean;
protected wordCountTimeout: any;
protected element: HTMLElement;
@ -61,9 +62,7 @@ export class AddonModAssignSubmissionOnlineTextComponent extends AddonModAssignS
// No offline data found, return online text.
return this.assignProvider.getSubmissionPluginText(this.plugin);
}).then((text) => {
// We receive them as strings, convert to int.
this.configs.wordlimit = parseInt(this.configs.wordlimit, 10);
this.configs.wordlimitenabled = parseInt(this.configs.wordlimitenabled, 10);
this.wordLimitEnabled = !!parseInt(this.configs.wordlimitenabled, 10);
// Set the text.
this.text = text;
@ -85,7 +84,7 @@ export class AddonModAssignSubmissionOnlineTextComponent extends AddonModAssignS
}
// Calculate initial words.
if (this.configs.wordlimitenabled) {
if (this.wordLimitEnabled) {
this.words = this.textUtils.countWords(text);
}
}).finally(() => {
@ -100,7 +99,7 @@ export class AddonModAssignSubmissionOnlineTextComponent extends AddonModAssignS
*/
onChange(text: string): void {
// Count words if needed.
if (this.configs.wordlimitenabled) {
if (this.wordLimitEnabled) {
// Cancel previous wait.
clearTimeout(this.wordCountTimeout);

View File

@ -18,7 +18,9 @@ import { TranslateService } from '@ngx-translate/core';
import { CoreSitesProvider } from '@providers/sites';
import { CoreWSProvider } from '@providers/ws';
import { CoreTextUtilsProvider } from '@providers/utils/text';
import { AddonModAssignProvider } from '../../../providers/assign';
import {
AddonModAssignProvider, AddonModAssignAssign, AddonModAssignSubmission, AddonModAssignPlugin
} from '../../../providers/assign';
import { AddonModAssignOfflineProvider } from '../../../providers/assign-offline';
import { AddonModAssignHelperProvider } from '../../../providers/helper';
import { AddonModAssignSubmissionHandler } from '../../../providers/submission-delegate';
@ -46,7 +48,8 @@ export class AddonModAssignSubmissionOnlineTextHandler implements AddonModAssign
* @param plugin The plugin object.
* @return Boolean or promise resolved with boolean: whether it can be edited in offline.
*/
canEditOffline(assign: any, submission: any, plugin: any): boolean | Promise<boolean> {
canEditOffline(assign: AddonModAssignAssign, submission: AddonModAssignSubmission,
plugin: AddonModAssignPlugin): boolean | Promise<boolean> {
// This plugin uses Moodle filters, it cannot be edited in offline.
return false;
}
@ -62,7 +65,9 @@ export class AddonModAssignSubmissionOnlineTextHandler implements AddonModAssign
* @param siteId Site ID. If not defined, current site.
* @return If the function is async, it should return a Promise resolved when done.
*/
copySubmissionData(assign: any, plugin: any, pluginData: any, userId?: number, siteId?: string): void | Promise<any> {
copySubmissionData(assign: AddonModAssignAssign, plugin: AddonModAssignPlugin, pluginData: any,
userId?: number, siteId?: string): void | Promise<any> {
const text = this.assignProvider.getSubmissionPluginText(plugin, true),
files = this.assignProvider.getSubmissionPluginAttachments(plugin);
let promise;
@ -93,7 +98,7 @@ export class AddonModAssignSubmissionOnlineTextHandler implements AddonModAssign
* @param edit Whether the user is editing.
* @return The component (or promise resolved with component) to use, undefined if not found.
*/
getComponent(injector: Injector, plugin: any, edit?: boolean): any | Promise<any> {
getComponent(injector: Injector, plugin: AddonModAssignPlugin, edit?: boolean): any | Promise<any> {
return AddonModAssignSubmissionOnlineTextComponent;
}
@ -107,7 +112,8 @@ export class AddonModAssignSubmissionOnlineTextHandler implements AddonModAssign
* @param siteId Site ID. If not defined, current site.
* @return The files (or promise resolved with the files).
*/
getPluginFiles(assign: any, submission: any, plugin: any, siteId?: string): any[] | Promise<any[]> {
getPluginFiles(assign: AddonModAssignAssign, submission: AddonModAssignSubmission,
plugin: AddonModAssignPlugin, siteId?: string): any[] | Promise<any[]> {
return this.assignProvider.getSubmissionPluginAttachments(plugin);
}
@ -118,7 +124,7 @@ export class AddonModAssignSubmissionOnlineTextHandler implements AddonModAssign
* @param plugin The plugin object.
* @return The size (or promise resolved with size).
*/
getSizeForCopy(assign: any, plugin: any): number | Promise<number> {
getSizeForCopy(assign: AddonModAssignAssign, plugin: AddonModAssignPlugin): number | Promise<number> {
const text = this.assignProvider.getSubmissionPluginText(plugin, true),
files = this.assignProvider.getSubmissionPluginAttachments(plugin),
promises = [];
@ -153,7 +159,8 @@ export class AddonModAssignSubmissionOnlineTextHandler implements AddonModAssign
* @param inputData Data entered by the user for the submission.
* @return The size (or promise resolved with size).
*/
getSizeForEdit(assign: any, submission: any, plugin: any, inputData: any): number | Promise<number> {
getSizeForEdit(assign: AddonModAssignAssign, submission: AddonModAssignSubmission,
plugin: AddonModAssignPlugin, inputData: any): number | Promise<number> {
const text = this.assignProvider.getSubmissionPluginText(plugin, true);
return text.length;
@ -182,7 +189,9 @@ export class AddonModAssignSubmissionOnlineTextHandler implements AddonModAssign
* @param inputData Data entered by the user for the submission.
* @return Boolean (or promise resolved with boolean): whether the data has changed.
*/
hasDataChanged(assign: any, submission: any, plugin: any, inputData: any): boolean | Promise<boolean> {
hasDataChanged(assign: AddonModAssignAssign, submission: AddonModAssignSubmission,
plugin: AddonModAssignPlugin, inputData: any): boolean | Promise<boolean> {
// Get the original text from plugin or offline.
return this.assignOfflineProvider.getSubmission(assign.id, submission.userid).catch(() => {
// No offline data found.
@ -234,7 +243,8 @@ export class AddonModAssignSubmissionOnlineTextHandler implements AddonModAssign
* @param siteId Site ID. If not defined, current site.
* @return If the function is async, it should return a Promise resolved when done.
*/
prepareSubmissionData(assign: any, submission: any, plugin: any, inputData: any, pluginData: any, offline?: boolean,
prepareSubmissionData(assign: AddonModAssignAssign, submission: AddonModAssignSubmission,
plugin: AddonModAssignPlugin, inputData: any, pluginData: any, offline?: boolean,
userId?: number, siteId?: string): void | Promise<any> {
let text = this.getTextToSubmit(plugin, inputData);
@ -274,8 +284,8 @@ export class AddonModAssignSubmissionOnlineTextHandler implements AddonModAssign
* @param siteId Site ID. If not defined, current site.
* @return If the function is async, it should return a Promise resolved when done.
*/
prepareSyncData(assign: any, submission: any, plugin: any, offlineData: any, pluginData: any, siteId?: string)
: void | Promise<any> {
prepareSyncData(assign: AddonModAssignAssign, submission: AddonModAssignSubmission,
plugin: AddonModAssignPlugin, offlineData: any, pluginData: any, siteId?: string): void | Promise<any> {
const textData = offlineData && offlineData.plugindata && offlineData.plugindata.onlinetext_editor;
if (textData) {

View File

@ -121,7 +121,7 @@ export class AddonModBookIndexComponent extends CoreCourseModuleMainResourceComp
// Try to get the book data.
promises.push(this.bookProvider.getBook(this.courseId, this.module.id).then((book) => {
this.dataRetrieved.emit(book);
this.description = book.intro || this.description;
this.description = book.intro;
}).catch(() => {
// Ignore errors since this WS isn't available in some Moodle versions.
}));

View File

@ -25,38 +25,7 @@ import { CoreCourseProvider } from '@core/course/providers/course';
import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper';
import { CoreSite } from '@classes/site';
import { CoreTagItem } from '@core/tag/providers/tag';
/**
* A book chapter inside the toc list.
*/
export interface AddonModBookTocChapter {
/**
* ID to identify the chapter.
*/
id: string;
/**
* Chapter's title.
*/
title: string;
/**
* The chapter's level.
*/
level: number;
}
/**
* Map of book contents. For each chapter it has its index URL and the list of paths of the files the chapter has. Each path
* is identified by the relative path in the book, and the value is the URL of the file.
*/
export type AddonModBookContentsMap = {
[chapter: string]: {
indexUrl?: string,
paths: {[path: string]: string},
tags?: CoreTagItem[]
}
};
import { CoreWSExternalWarning, CoreWSExternalFile } from '@providers/ws';
/**
* Service that provides some features for books.
@ -83,7 +52,7 @@ export class AddonModBookProvider {
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved when the book is retrieved.
*/
getBook(courseId: number, cmId: number, siteId?: string): Promise<any> {
getBook(courseId: number, cmId: number, siteId?: string): Promise<AddonModBookBook> {
return this.getBookByField(courseId, 'coursemodule', cmId, siteId);
}
@ -96,7 +65,7 @@ export class AddonModBookProvider {
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved when the book is retrieved.
*/
protected getBookByField(courseId: number, key: string, value: any, siteId?: string): Promise<any> {
protected getBookByField(courseId: number, key: string, value: any, siteId?: string): Promise<AddonModBookBook> {
return this.sitesProvider.getSite(siteId).then((site) => {
const params = {
courseids: [courseId]
@ -106,7 +75,9 @@ export class AddonModBookProvider {
updateFrequency: CoreSite.FREQUENCY_RARELY
};
return site.read('mod_book_get_books_by_courses', params, preSets).then((response) => {
return site.read('mod_book_get_books_by_courses', params, preSets)
.then((response: AddonModBookGetBooksByCoursesResult): any => {
// Search the book.
if (response && response.books) {
for (const i in response.books) {
@ -401,3 +372,66 @@ export class AddonModBookProvider {
{chapterid: chapterId}, siteId);
}
}
/**
* A book chapter inside the toc list.
*/
export type AddonModBookTocChapter = {
/**
* ID to identify the chapter.
*/
id: string;
/**
* Chapter's title.
*/
title: string;
/**
* The chapter's level.
*/
level: number;
};
/**
* Map of book contents. For each chapter it has its index URL and the list of paths of the files the chapter has. Each path
* is identified by the relative path in the book, and the value is the URL of the file.
*/
export type AddonModBookContentsMap = {
[chapter: string]: {
indexUrl?: string,
paths: {[path: string]: string},
tags?: CoreTagItem[]
}
};
/**
* Book returned by mod_book_get_books_by_courses.
*/
export type AddonModBookBook = {
id: number; // Book id.
coursemodule: number; // Course module id.
course: number; // Course id.
name: string; // Book name.
intro: string; // The Book intro.
introformat: number; // Intro format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
introfiles?: CoreWSExternalFile[]; // @since 3.2.
numbering: number; // Book numbering configuration.
navstyle: number; // Book navigation style configuration.
customtitles: number; // Book custom titles type.
revision?: number; // Book revision.
timecreated?: number; // Time of creation.
timemodified?: number; // Time of last modification.
section?: number; // Course section id.
visible?: boolean; // Visible.
groupmode?: number; // Group mode.
groupingid?: number; // Group id.
};
/**
* Result of WS mod_book_get_books_by_courses.
*/
export type AddonModBookGetBooksByCoursesResult = {
books: AddonModBookBook[];
warnings?: CoreWSExternalWarning[];
};

View File

@ -16,7 +16,7 @@ import { Component, Injector } from '@angular/core';
import { NavController } from 'ionic-angular';
import { CoreCourseModuleMainActivityComponent } from '@core/course/classes/main-activity-component';
import { CoreTimeUtilsProvider } from '@providers/utils/time';
import { AddonModChatProvider } from '../../providers/chat';
import { AddonModChatProvider, AddonModChatChat } from '../../providers/chat';
/**
* Component that displays a chat.
@ -29,7 +29,7 @@ export class AddonModChatIndexComponent extends CoreCourseModuleMainActivityComp
component = AddonModChatProvider.COMPONENT;
moduleName = 'chat';
chat: any;
chat: AddonModChatChat;
chatInfo: any;
protected title: string;
@ -66,7 +66,7 @@ export class AddonModChatIndexComponent extends CoreCourseModuleMainActivityComp
protected fetchContent(refresh: boolean = false, sync: boolean = false, showErrors: boolean = false): Promise<any> {
return this.chatProvider.getChat(this.courseId, this.module.id).then((chat) => {
this.chat = chat;
this.description = chat.intro || chat.description;
this.description = chat.intro;
const now = this.timeUtils.timestamp();
const span = chat.chattime - now;

View File

@ -20,7 +20,7 @@ import { CoreLoggerProvider } from '@providers/logger';
import { CoreSitesProvider } from '@providers/sites';
import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { CoreTextUtilsProvider } from '@providers/utils/text';
import { AddonModChatProvider } from '../../providers/chat';
import { AddonModChatProvider, AddonModChatMessageWithUserData } from '../../providers/chat';
import { Network } from '@ionic-native/network';
import * as moment from 'moment';
@ -37,7 +37,7 @@ export class AddonModChatChatPage {
loaded = false;
title: string;
messages = [];
messages: AddonModChatMessageWithUserData[] = [];
newMessage: string;
polling: any;
isOnline: boolean;
@ -46,7 +46,7 @@ export class AddonModChatChatPage {
protected logger;
protected courseId: number;
protected chatId: number;
protected sessionId: number;
protected sessionId: string;
protected lastTime = 0;
protected oldContentHeight = 0;
protected onlineObserver: any;
@ -131,9 +131,9 @@ export class AddonModChatChatPage {
/**
* Convenience function to login the user.
*
* @return Resolved when done.
* @return Promise resolved when done.
*/
protected loginUser(): Promise<any> {
protected loginUser(): Promise<void> {
return this.chatProvider.loginUser(this.chatId).then((sessionId) => {
this.sessionId = sessionId;
});
@ -144,12 +144,12 @@ export class AddonModChatChatPage {
*
* @return Promise resolved when done.
*/
protected fetchMessages(): Promise<any> {
protected fetchMessages(): Promise<void> {
return this.chatProvider.getLatestMessages(this.sessionId, this.lastTime).then((messagesInfo) => {
this.lastTime = messagesInfo.chatnewlasttime || 0;
return this.chatProvider.getMessagesUserData(messagesInfo.messages, this.courseId).then((messages) => {
this.messages = this.messages.concat(messages);
this.messages = this.messages.concat(<AddonModChatMessageWithUserData[]> messages);
if (messages.length) {
// New messages or beeps, scroll to bottom.
setTimeout(() => this.scrollToBottom());
@ -190,7 +190,7 @@ export class AddonModChatChatPage {
*
* @return Promised resolved when done.
*/
protected fetchMessagesInterval(): Promise<any> {
protected fetchMessagesInterval(): Promise<void> {
this.logger.debug('Polling for messages');
if (!this.isOnline || this.pollingRunning) {
// Obviously we cannot check for new messages when the app is offline.
@ -225,7 +225,7 @@ export class AddonModChatChatPage {
* @param prevMessage Previous message object.
* @return True if messages are from diferent days, false othetwise.
*/
showDate(message: any, prevMessage: any): boolean {
showDate(message: AddonModChatMessageWithUserData, prevMessage: AddonModChatMessageWithUserData): boolean {
if (!prevMessage) {
return true;
}
@ -267,7 +267,7 @@ export class AddonModChatChatPage {
});
}
reconnect(): Promise<any> {
reconnect(): Promise<void> {
const modal = this.domUtils.showModalLoading();
// Call startPolling would take a while for the first execution, so we'll execute it manually to check if it works now.

View File

@ -15,7 +15,7 @@
import { Component } from '@angular/core';
import { IonicPage, NavParams } from 'ionic-angular';
import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { AddonModChatProvider } from '../../providers/chat';
import { AddonModChatProvider, AddonModChatSessionMessageWithUserData } from '../../providers/chat';
import * as moment from 'moment';
/**
@ -34,7 +34,7 @@ export class AddonModChatSessionMessagesPage {
protected sessionEnd: number;
protected groupId: number;
protected loaded = false;
protected messages = [];
protected messages: AddonModChatSessionMessageWithUserData[] = [];
constructor(navParams: NavParams, private domUtils: CoreDomUtilsProvider, private chatProvider: AddonModChatProvider) {
this.courseId = navParams.get('courseId');
@ -55,7 +55,7 @@ export class AddonModChatSessionMessagesPage {
return this.chatProvider.getSessionMessages(this.chatId, this.sessionStart, this.sessionEnd, this.groupId)
.then((messages) => {
return this.chatProvider.getMessagesUserData(messages, this.courseId).then((messages) => {
this.messages = messages;
this.messages = <AddonModChatSessionMessageWithUserData[]> messages;
});
}).catch((error) => {
this.domUtils.showErrorModalDefault(error, 'core.errorloadingcontent', true);
@ -84,7 +84,7 @@ export class AddonModChatSessionMessagesPage {
* @param prevMessage Previous message object.
* @return True if messages are from diferent days, false othetwise.
*/
showDate(message: any, prevMessage: any): boolean {
showDate(message: AddonModChatSessionMessageWithUserData, prevMessage: AddonModChatSessionMessageWithUserData): boolean {
if (!prevMessage) {
return true;
}

View File

@ -20,7 +20,7 @@ import { CoreUserProvider } from '@core/user/providers/user';
import { CoreGroupsProvider, CoreGroupInfo } from '@providers/groups';
import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { CoreUtilsProvider } from '@providers/utils/utils';
import { AddonModChatProvider } from '../../providers/chat';
import { AddonModChatProvider, AddonModChatSession, AddonModChatSessionUser } from '../../providers/chat';
/**
* Page that displays list of chat sessions.
@ -73,13 +73,13 @@ export class AddonModChatSessionsPage {
this.groupId = this.groupsProvider.validateGroupId(this.groupId, groupInfo);
return this.chatProvider.getSessions(this.chatId, this.groupId, this.showAll);
}).then((sessions) => {
}).then((sessions: AddonModChatSessionFormatted[]) => {
// Fetch user profiles.
const promises = [];
sessions.forEach((session) => {
session.duration = session.sessionend - session.sessionstart;
session.sessionusers.forEach((sessionUser) => {
session.sessionusers.forEach((sessionUser: AddonModChatUserSessionFormatted) => {
if (!sessionUser.userfullname) {
// The WS does not return the user name, fetch user profile.
promises.push(this.userProvider.getProfile(sessionUser.userid, this.courseId, true).then((user) => {
@ -156,3 +156,18 @@ export class AddonModChatSessionsPage {
$event.stopPropagation();
}
}
/**
* Fields added to chat session in this view.
*/
type AddonModChatSessionFormatted = AddonModChatSession & {
duration?: number; // Session duration.
allsessionusers?: AddonModChatUserSessionFormatted[]; // All session users.
};
/**
* Fields added to user session in this view.
*/
type AddonModChatUserSessionFormatted = AddonModChatSessionUser & {
userfullname?: string; // User full name.
};

View File

@ -17,7 +17,7 @@ import { IonicPage, NavParams, ViewController } from 'ionic-angular';
import { CoreAppProvider } from '@providers/app';
import { CoreSitesProvider } from '@providers/sites';
import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { AddonModChatProvider } from '../../providers/chat';
import { AddonModChatProvider, AddonModChatUser } from '../../providers/chat';
import { Network } from '@ionic-native/network';
/**
@ -30,12 +30,12 @@ import { Network } from '@ionic-native/network';
})
export class AddonModChatUsersPage {
users = [];
users: AddonModChatUser[] = [];
usersLoaded = false;
currentUserId: number;
isOnline: boolean;
protected sessionId: number;
protected sessionId: string;
protected onlineObserver: any;
constructor(navParams: NavParams, network: Network, zone: NgZone, private appProvider: CoreAppProvider,
@ -77,7 +77,7 @@ export class AddonModChatUsersPage {
*
* @param user User object.
*/
talkTo(user: any): void {
talkTo(user: AddonModChatUser): void {
this.viewCtrl.dismiss({talkTo: user.fullname});
}
@ -86,7 +86,7 @@ export class AddonModChatUsersPage {
*
* @param user User object.
*/
beepTo(user: any): void {
beepTo(user: AddonModChatUser): void {
this.viewCtrl.dismiss({beepTo: user.id});
}

View File

@ -19,6 +19,7 @@ import { CoreUserProvider } from '@core/user/providers/user';
import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper';
import { CoreUtilsProvider } from '@providers/utils/utils';
import { CoreSite, CoreSiteWSPreSets } from '@classes/site';
import { CoreWSExternalWarning, CoreWSExternalFile } from '@providers/ws';
/**
* Service that provides some features for chats.
@ -41,7 +42,7 @@ export class AddonModChatProvider {
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved when the chat is retrieved.
*/
getChat(courseId: number, cmId: number, siteId?: string): Promise<any> {
getChat(courseId: number, cmId: number, siteId?: string): Promise<AddonModChatChat> {
return this.sitesProvider.getSite(siteId).then((site) => {
const params = {
courseids: [courseId]
@ -51,7 +52,9 @@ export class AddonModChatProvider {
updateFrequency: CoreSite.FREQUENCY_RARELY
};
return site.read('mod_chat_get_chats_by_courses', params, preSets).then((response) => {
return site.read('mod_chat_get_chats_by_courses', params, preSets)
.then((response: AddonModChatGetChatsByCoursesResult): any => {
if (response.chats) {
const chat = response.chats.find((chat) => chat.coursemodule == cmId);
if (chat) {
@ -70,12 +73,14 @@ export class AddonModChatProvider {
* @param chatId Chat instance ID.
* @return Promise resolved when the WS is executed.
*/
loginUser(chatId: number): Promise<any> {
loginUser(chatId: number): Promise<string> {
const params = {
chatid: chatId
};
return this.sitesProvider.getCurrentSite().write('mod_chat_login_user', params).then((response) => {
return this.sitesProvider.getCurrentSite().write('mod_chat_login_user', params)
.then((response: AddonModChatLoginUserResult): any => {
if (response.chatsid) {
return response.chatsid;
}
@ -108,14 +113,16 @@ export class AddonModChatProvider {
* @param beepUserId Beep user ID.
* @return Promise resolved when the WS is executed.
*/
sendMessage(sessionId: number, message: string, beepUserId: number): Promise<any> {
sendMessage(sessionId: string, message: string, beepUserId: number): Promise<number> {
const params = {
chatsid: sessionId,
messagetext: message,
beepid: beepUserId
};
return this.sitesProvider.getCurrentSite().write('mod_chat_send_chat_message', params).then((response) => {
return this.sitesProvider.getCurrentSite().write('mod_chat_send_chat_message', params)
.then((response: AddonModChatSendChatMessageResult): any => {
if (response.messageid) {
return response.messageid;
}
@ -131,7 +138,7 @@ export class AddonModChatProvider {
* @param lastTime Last time when messages were retrieved.
* @return Promise resolved when the WS is executed.
*/
getLatestMessages(sessionId: number, lastTime: number): Promise<any> {
getLatestMessages(sessionId: string, lastTime: number): Promise<AddonModChatGetChatLatestMessagesResult> {
const params = {
chatsid: sessionId,
chatlasttime: lastTime
@ -149,8 +156,10 @@ export class AddonModChatProvider {
* @param courseId ID of the course the messages belong to.
* @return Promise always resolved with the formatted messages.
*/
getMessagesUserData(messages: any[], courseId: number): Promise<any> {
const promises = messages.map((message) => {
getMessagesUserData(messages: (AddonModChatMessage | AddonModChatSessionMessage)[], courseId: number)
: Promise<(AddonModChatMessageWithUserData | AddonModChatSessionMessageWithUserData)[]> {
const promises = messages.map((message: AddonModChatMessageWithUserData | AddonModChatSessionMessageWithUserData) => {
return this.userProvider.getProfile(message.userid, courseId, true).then((user) => {
message.userfullname = user.fullname;
message.userprofileimageurl = user.profileimageurl;
@ -171,7 +180,7 @@ export class AddonModChatProvider {
* @param sessionId Chat sessiond ID.
* @return Promise resolved when the WS is executed.
*/
getChatUsers(sessionId: number): Promise<any> {
getChatUsers(sessionId: string): Promise<AddonModChatGetChatUsersResult> {
const params = {
chatsid: sessionId
};
@ -206,7 +215,8 @@ export class AddonModChatProvider {
* @since 3.5
*/
getSessions(chatId: number, groupId: number = 0, showAll: boolean = false, ignoreCache: boolean = false, siteId?: string):
Promise<any[]> {
Promise<AddonModChatSession[]> {
return this.sitesProvider.getSite(siteId).then((site) => {
const params = {
chatid: chatId,
@ -222,7 +232,7 @@ export class AddonModChatProvider {
preSets.emergencyCache = false;
}
return site.read('mod_chat_get_sessions', params, preSets).then((response) => {
return site.read('mod_chat_get_sessions', params, preSets).then((response: AddonModChatGetSessionsResult): any => {
if (!response || !response.sessions) {
return Promise.reject(null);
}
@ -245,7 +255,8 @@ export class AddonModChatProvider {
* @since 3.5
*/
getSessionMessages(chatId: number, sessionStart: number, sessionEnd: number, groupId: number = 0, ignoreCache: boolean = false,
siteId?: string): Promise<any[]> {
siteId?: string): Promise<AddonModChatSessionMessage[]> {
return this.sitesProvider.getSite(siteId).then((site) => {
const params = {
chatid: chatId,
@ -262,7 +273,9 @@ export class AddonModChatProvider {
preSets.emergencyCache = false;
}
return site.read('mod_chat_get_session_messages', params, preSets).then((response) => {
return site.read('mod_chat_get_session_messages', params, preSets)
.then((response: AddonModChatGetSessionMessagesResult): any => {
if (!response || !response.messages) {
return Promise.reject(null);
}
@ -390,3 +403,152 @@ export class AddonModChatProvider {
return this.ROOT_CACHE_KEY + 'sessionsMessages:' + chatId + ':';
}
}
/**
* Chat returned by mod_chat_get_chats_by_courses.
*/
export type AddonModChatChat = {
id: number; // Chat id.
coursemodule: number; // Course module id.
course: number; // Course id.
name: string; // Chat name.
intro: string; // The Chat intro.
introformat: number; // Intro format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
introfiles?: CoreWSExternalFile[]; // @since 3.2.
chatmethod?: string; // Chat method (sockets, ajax, header_js).
keepdays?: number; // Keep days.
studentlogs?: number; // Student logs visible to everyone.
chattime?: number; // Chat time.
schedule?: number; // Schedule type.
timemodified?: number; // Time of last modification.
section?: number; // Course section id.
visible?: boolean; // Visible.
groupmode?: number; // Group mode.
groupingid?: number; // Group id.
};
/**
* Chat user returned by mod_chat_get_chat_users.
*/
export type AddonModChatUser = {
id: number; // User id.
fullname: string; // User full name.
profileimageurl: string; // User picture URL.
};
/**
* Meessage returned by mod_chat_get_chat_latest_messages.
*/
export type AddonModChatMessage = {
id: number; // Message id.
userid: number; // User id.
system: boolean; // True if is a system message (like user joined).
message: string; // Message text.
timestamp: number; // Timestamp for the message.
};
/**
* Message with user data
*/
export type AddonModChatMessageWithUserData = AddonModChatMessage & AddonModChatMessageUserData;
/**
* Chat session.
*/
export type AddonModChatSession = {
sessionstart: number; // Session start time.
sessionend: number; // Session end time.
sessionusers: AddonModChatSessionUser[]; // Session users.
iscomplete: boolean; // Whether the session is completed or not.
};
/**
* Chat user returned by mod_chat_get_sessions.
*/
export type AddonModChatSessionUser = {
userid: number; // User id.
messagecount: number; // Number of messages in the session.
};
/**
* Message returned by mod_chat_get_session_messages.
*/
export type AddonModChatSessionMessage = {
id: number; // The message record id.
chatid: number; // The chat id.
userid: number; // The user who wrote the message.
groupid: number; // The group this message belongs to.
issystem: boolean; // Whether is a system message or not.
message: string; // The message text.
timestamp: number; // The message timestamp (indicates when the message was sent).
};
/**
* Message with user data
*/
export type AddonModChatSessionMessageWithUserData = AddonModChatSessionMessage & AddonModChatMessageUserData;
/**
* Result of WS mod_chat_get_chats_by_courses.
*/
export type AddonModChatGetChatsByCoursesResult = {
chats: AddonModChatChat[];
warnings?: CoreWSExternalWarning[];
};
/**
* Result of WS mod_chat_get_chat_users.
*/
export type AddonModChatGetChatUsersResult = {
users: AddonModChatUser[]; // List of users.
warnings?: CoreWSExternalWarning[];
};
/**
* Result of WS mod_chat_get_sessions.
*/
export type AddonModChatGetSessionsResult = {
sessions: AddonModChatSession[]; // List of sessions.
warnings?: CoreWSExternalWarning[];
};
/**
* Result of WS mod_chat_get_session_messages.
*/
export type AddonModChatGetSessionMessagesResult = {
messages: AddonModChatSessionMessage[];
warnings?: CoreWSExternalWarning[];
};
/**
* Result of WS mod_chat_send_chat_message.
*/
export type AddonModChatSendChatMessageResult = {
messageid: number; // Message sent id.
warnings?: CoreWSExternalWarning[];
};
/**
* Result of WS mod_chat_get_chat_latest_messages.
*/
export type AddonModChatGetChatLatestMessagesResult = {
messages: AddonModChatMessage[]; // List of messages.
chatnewlasttime: number; // New last time.
warnings?: CoreWSExternalWarning[];
};
/**
* Result of WS mod_chat_login_user.
*/
export type AddonModChatLoginUserResult = {
chatsid: string; // Unique chat session id.
warnings?: CoreWSExternalWarning[];
};
/**
* User data added to messages.
*/
type AddonModChatMessageUserData = {
userfullname?: string; // Calculated in the app. Full name of the user who wrote the message.
userprofileimageurl?: string; // Calculated in the app. Full name of the user who wrote the message.
};

View File

@ -23,7 +23,7 @@ import { CoreUtilsProvider } from '@providers/utils/utils';
import { CoreCourseProvider } from '@core/course/providers/course';
import { CoreCourseActivityPrefetchHandlerBase } from '@core/course/classes/activity-prefetch-handler';
import { CoreUserProvider } from '@core/user/providers/user';
import { AddonModChatProvider } from './chat';
import { AddonModChatProvider, AddonModChatChat } from './chat';
/**
* Handler to prefetch chats.
@ -116,12 +116,12 @@ export class AddonModChatPrefetchHandler extends CoreCourseActivityPrefetchHandl
*/
protected prefetchChat(module: any, courseId: number, single: boolean, siteId: string): Promise<any> {
// Prefetch chat and group info.
const promises = [
const promises: Promise<any>[] = [
this.chatProvider.getChat(courseId, module.id, siteId),
this.groupsProvider.getActivityGroupInfo(module.id, false, undefined, siteId)
];
return Promise.all(promises).then(([chat, groupInfo]: [any, CoreGroupInfo]) => {
return Promise.all(promises).then(([chat, groupInfo]: [AddonModChatChat, CoreGroupInfo]) => {
const promises = [];
let groupIds = [0];

View File

@ -19,13 +19,13 @@
<!-- Activity availability messages -->
<ion-card class="core-info-card" icon-start *ngIf="choiceNotOpenYet">
<ion-icon name="information-circle"></ion-icon>
<p *ngIf="options && options.length">{{ 'addon.mod_choice.previewonly' | translate:{$a: choice.openTimeReadable} }}</p>
<p *ngIf="!options || !options.length">{{ 'addon.mod_choice.notopenyet' | translate:{$a: choice.openTimeReadable} }}</p>
<p *ngIf="options && options.length">{{ 'addon.mod_choice.previewonly' | translate:{$a: openTimeReadable} }}</p>
<p *ngIf="!options || !options.length">{{ 'addon.mod_choice.notopenyet' | translate:{$a: openTimeReadable} }}</p>
</ion-card>
<ion-card class="core-info-card" icon-start *ngIf="choiceClosed">
<ion-icon name="information-circle"></ion-icon>
<p *ngIf="options && options.length">{{ 'addon.mod_choice.yourselection' | translate }} <core-format-text [text]="options[0].text"></core-format-text></p>
<p>{{ 'addon.mod_choice.expired' | translate:{$a: choice.closeTimeReadable} }}</p>
<p>{{ 'addon.mod_choice.expired' | translate:{$a: closeTimeReadable} }}</p>
</ion-card>
<!-- Choice done in offline but not synchronized -->
@ -80,7 +80,7 @@
<ion-item-group *ngFor="let result of results">
<ion-item-divider text-wrap>
<h2><core-format-text [text]="result.text"></core-format-text></h2>
<p>{{ 'addon.mod_choice.numberofuser' | translate }}: {{ result.numberofuser }} ({{ 'core.percentagenumber' | translate: {$a: result.percentageamount} }})</p>
<p>{{ 'addon.mod_choice.numberofuser' | translate }}: {{ result.numberofuser }} ({{ 'core.percentagenumber' | translate: {$a: result.percentageamountfixed} }})</p>
</ion-item-divider>
<a ion-item *ngFor="let user of result.userresponses" core-user-link [courseId]="courseid" [userId]="user.userid" [title]="user.fullname" text-wrap>
<ion-avatar core-user-avatar [user]="user" item-start [courseId]="courseid"></ion-avatar>

View File

@ -16,7 +16,7 @@ import { Component, Optional, Injector } from '@angular/core';
import { Content } from 'ionic-angular';
import { CoreTimeUtilsProvider } from '@providers/utils/time';
import { CoreCourseModuleMainActivityComponent } from '@core/course/classes/main-activity-component';
import { AddonModChoiceProvider } from '../../providers/choice';
import { AddonModChoiceProvider, AddonModChoiceChoice, AddonModChoiceOption, AddonModChoiceResult } from '../../providers/choice';
import { AddonModChoiceOfflineProvider } from '../../providers/offline';
import { AddonModChoiceSyncProvider } from '../../providers/sync';
@ -31,9 +31,9 @@ export class AddonModChoiceIndexComponent extends CoreCourseModuleMainActivityCo
component = AddonModChoiceProvider.COMPONENT;
moduleName = 'choice';
choice: any;
options = [];
selectedOption: any;
choice: AddonModChoiceChoice;
options: AddonModChoiceOption[] = [];
selectedOption: {id: number};
choiceNotOpenYet = false;
choiceClosed = false;
canEdit = false;
@ -43,6 +43,8 @@ export class AddonModChoiceIndexComponent extends CoreCourseModuleMainActivityCo
labels = [];
results = [];
publishInfo: string; // Message explaining the user what will happen with his choices.
openTimeReadable: string;
closeTimeReadable: string;
protected userId: number;
protected syncEventName = AddonModChoiceSyncProvider.AUTO_SYNCED;
@ -122,12 +124,12 @@ export class AddonModChoiceIndexComponent extends CoreCourseModuleMainActivityCo
return this.choiceProvider.getChoice(this.courseId, this.module.id).then((choice) => {
this.choice = choice;
this.choice.timeopen = parseInt(choice.timeopen) * 1000;
this.choice.openTimeReadable = this.timeUtils.userDate(choice.timeopen);
this.choice.timeclose = parseInt(choice.timeclose) * 1000;
this.choice.closeTimeReadable = this.timeUtils.userDate(choice.timeclose);
this.choice.timeopen = choice.timeopen * 1000;
this.choice.timeclose = choice.timeclose * 1000;
this.openTimeReadable = this.timeUtils.userDate(choice.timeopen);
this.closeTimeReadable = this.timeUtils.userDate(choice.timeclose);
this.description = choice.intro || choice.description;
this.description = choice.intro;
this.choiceNotOpenYet = choice.timeopen && choice.timeopen > this.now;
this.choiceClosed = choice.timeclose && choice.timeclose <= this.now;
@ -175,7 +177,7 @@ export class AddonModChoiceIndexComponent extends CoreCourseModuleMainActivityCo
if (hasOffline) {
promise = this.choiceOffline.getResponse(this.choice.id).then((response) => {
const optionsKeys = {};
const optionsKeys: {[id: number]: AddonModChoiceOption} = {};
options.forEach((option) => {
optionsKeys[option.id] = option;
});
@ -223,7 +225,7 @@ export class AddonModChoiceIndexComponent extends CoreCourseModuleMainActivityCo
promise = Promise.resolve(options);
}
promise.then((options) => {
promise.then((options: AddonModChoiceOption[]) => {
const isOpen = this.isChoiceOpen();
let hasAnswered = false;
@ -291,11 +293,11 @@ export class AddonModChoiceIndexComponent extends CoreCourseModuleMainActivityCo
let hasVotes = false;
this.data = [];
this.labels = [];
results.forEach((result) => {
results.forEach((result: AddonModChoiceResultFormatted) => {
if (result.numberofuser > 0) {
hasVotes = true;
}
result.percentageamount = parseFloat(result.percentageamount).toFixed(1);
result.percentageamountfixed = result.percentageamount.toFixed(1);
this.data.push(result.numberofuser);
this.labels.push(result.text);
});
@ -429,3 +431,10 @@ export class AddonModChoiceIndexComponent extends CoreCourseModuleMainActivityCo
return result.updated;
}
}
/**
* Choice result with some calculated data.
*/
export type AddonModChoiceResultFormatted = AddonModChoiceResult & {
percentageamountfixed: string; // Percentage of users answers with fixed decimals.
};

View File

@ -20,6 +20,7 @@ import { CoreFilepoolProvider } from '@providers/filepool';
import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper';
import { AddonModChoiceOfflineProvider } from './offline';
import { CoreSite, CoreSiteWSPreSets } from '@classes/site';
import { CoreWSExternalWarning, CoreWSExternalFile } from '@providers/ws';
/**
* Service that provides some features for choices.
@ -118,7 +119,9 @@ export class AddonModChoiceProvider {
responses: responses
};
return site.write('mod_choice_delete_choice_responses', params).then((response) => {
return site.write('mod_choice_delete_choice_responses', params)
.then((response: AddonModChoiceDeleteChoiceResponsesResult) => {
// Other errors ocurring.
if (!response || response.status === false) {
return Promise.reject(this.utils.createFakeWSError(''));
@ -179,7 +182,7 @@ export class AddonModChoiceProvider {
* @return Promise resolved when the choice is retrieved.
*/
protected getChoiceByDataKey(siteId: string, courseId: number, key: string, value: any, forceCache?: boolean,
ignoreCache?: boolean): Promise<any> {
ignoreCache?: boolean): Promise<AddonModChoiceChoice> {
return this.sitesProvider.getSite(siteId).then((site) => {
const params = {
@ -198,7 +201,9 @@ export class AddonModChoiceProvider {
preSets.emergencyCache = false;
}
return site.read('mod_choice_get_choices_by_courses', params, preSets).then((response) => {
return site.read('mod_choice_get_choices_by_courses', params, preSets)
.then((response: AddonModChoiceGetChoicesByCoursesResult): any => {
if (response && response.choices) {
const currentChoice = response.choices.find((choice) => choice[key] == value);
if (currentChoice) {
@ -221,7 +226,8 @@ export class AddonModChoiceProvider {
* @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down).
* @return Promise resolved when the choice is retrieved.
*/
getChoice(courseId: number, cmId: number, siteId?: string, forceCache?: boolean, ignoreCache?: boolean): Promise<any> {
getChoice(courseId: number, cmId: number, siteId?: string, forceCache?: boolean, ignoreCache?: boolean)
: Promise<AddonModChoiceChoice> {
return this.getChoiceByDataKey(siteId, courseId, 'coursemodule', cmId, forceCache, ignoreCache);
}
@ -235,7 +241,8 @@ export class AddonModChoiceProvider {
* @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down).
* @return Promise resolved when the choice is retrieved.
*/
getChoiceById(courseId: number, choiceId: number, siteId?: string, forceCache?: boolean, ignoreCache?: boolean): Promise<any> {
getChoiceById(courseId: number, choiceId: number, siteId?: string, forceCache?: boolean, ignoreCache?: boolean)
: Promise<AddonModChoiceChoice> {
return this.getChoiceByDataKey(siteId, courseId, 'id', choiceId, forceCache, ignoreCache);
}
@ -247,7 +254,7 @@ export class AddonModChoiceProvider {
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved with choice options.
*/
getOptions(choiceId: number, ignoreCache?: boolean, siteId?: string): Promise<any> {
getOptions(choiceId: number, ignoreCache?: boolean, siteId?: string): Promise<AddonModChoiceOption[]> {
return this.sitesProvider.getSite(siteId).then((site) => {
const params = {
choiceid: choiceId
@ -262,7 +269,9 @@ export class AddonModChoiceProvider {
preSets.emergencyCache = false;
}
return site.read('mod_choice_get_choice_options', params, preSets).then((response) => {
return site.read('mod_choice_get_choice_options', params, preSets)
.then((response: AddonModChoiceGetChoiceOptionsResult): any => {
if (response.options) {
return response.options;
}
@ -280,7 +289,7 @@ export class AddonModChoiceProvider {
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved with choice results.
*/
getResults(choiceId: number, ignoreCache?: boolean, siteId?: string): Promise<any> {
getResults(choiceId: number, ignoreCache?: boolean, siteId?: string): Promise<AddonModChoiceResult[]> {
return this.sitesProvider.getSite(siteId).then((site) => {
const params = {
choiceid: choiceId
@ -294,7 +303,9 @@ export class AddonModChoiceProvider {
preSets.emergencyCache = false;
}
return site.read('mod_choice_get_choice_results', params, preSets).then((response) => {
return site.read('mod_choice_get_choice_results', params, preSets)
.then((response: AddonModChoiceGetChoiceResults): any => {
if (response.options) {
return response.options;
}
@ -456,3 +467,96 @@ export class AddonModChoiceProvider {
});
}
}
/**
* Choice returned by mod_choice_get_choices_by_courses.
*/
export type AddonModChoiceChoice = {
id: number; // Choice instance id.
coursemodule: number; // Course module id.
course: number; // Course id.
name: string; // Choice name.
intro: string; // The choice intro.
introformat: number; // Intro format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
introfiles?: CoreWSExternalFile[]; // @since 3.2.
publish?: boolean; // If choice is published.
showresults?: number; // 0 never, 1 after answer, 2 after close, 3 always.
display?: number; // Display mode (vertical, horizontal).
allowupdate?: boolean; // Allow update.
allowmultiple?: boolean; // Allow multiple choices.
showunanswered?: boolean; // Show users who not answered yet.
includeinactive?: boolean; // Include inactive users.
limitanswers?: boolean; // Limit unswers.
timeopen?: number; // Date of opening validity.
timeclose?: number; // Date of closing validity.
showpreview?: boolean; // Show preview before timeopen.
timemodified?: number; // Time of last modification.
completionsubmit?: boolean; // Completion on user submission.
section?: number; // Course section id.
visible?: boolean; // Visible.
groupmode?: number; // Group mode.
groupingid?: number; // Group id.
};
/**
* Option returned by mod_choice_get_choice_options.
*/
export type AddonModChoiceOption = {
id: number; // Option id.
text: string; // Text of the choice.
maxanswers: number; // Maximum number of answers.
displaylayout: boolean; // True for orizontal, otherwise vertical.
countanswers: number; // Number of answers.
checked: boolean; // We already answered.
disabled: boolean; // Option disabled.
};
/**
* Result returned by mod_choice_get_choice_results.
*/
export type AddonModChoiceResult = {
id: number; // Choice instance id.
text: string; // Text of the choice.
maxanswer: number; // Maximum number of answers.
userresponses: {
userid: number; // User id.
fullname: string; // User full name.
profileimageurl: string; // Profile user image url.
answerid?: number; // Answer id.
timemodified?: number; // Time of modification.
}[];
numberofuser: number; // Number of users answers.
percentageamount: number; // Percentage of users answers.
};
/**
* Result of WS mod_choice_get_choices_by_courses.
*/
export type AddonModChoiceGetChoicesByCoursesResult = {
choices: AddonModChoiceChoice[];
warnings?: CoreWSExternalWarning[];
};
/**
* Result of WS mod_choice_get_choice_options.
*/
export type AddonModChoiceGetChoiceOptionsResult = {
options: AddonModChoiceOption[]; // Options.
warnings?: CoreWSExternalWarning[];
};
/**
* Result of WS mod_choice_get_choice_results.
*/
export type AddonModChoiceGetChoiceResults = {
options: AddonModChoiceResult[];
warnings?: CoreWSExternalWarning[];
};
/**
* Result of WS mod_choice_delete_choice_responses.
*/
export type AddonModChoiceDeleteChoiceResponsesResult = {
status: boolean; // Status, true if everything went right.
warnings?: CoreWSExternalWarning[];
};

View File

@ -34,6 +34,7 @@ export class AddonModFolderIndexComponent extends CoreCourseModuleMainResourceCo
component = AddonModFolderProvider.COMPONENT;
canGetFolder: boolean;
contents: any;
moduleContents: any;
constructor(injector: Injector, private folderProvider: AddonModFolderProvider, private courseProvider: CoreCourseProvider,
private appProvider: CoreAppProvider, private folderHelper: AddonModFolderHelperProvider) {
@ -87,9 +88,9 @@ export class AddonModFolderIndexComponent extends CoreCourseModuleMainResourceCo
if (this.path) {
// Subfolder.
this.contents = module.contents;
this.contents = this.moduleContents;
} else {
this.contents = this.folderHelper.formatContents(module.contents);
this.contents = this.folderHelper.formatContents(this.moduleContents);
}
}
@ -105,7 +106,7 @@ export class AddonModFolderIndexComponent extends CoreCourseModuleMainResourceCo
if (this.canGetFolder) {
promise = this.folderProvider.getFolder(this.courseId, this.module.id).then((folder) => {
return this.courseProvider.loadModuleContents(this.module, this.courseId).then(() => {
folder.contents = this.module.contents;
this.moduleContents = this.module.contents;
return folder;
});
@ -117,17 +118,13 @@ export class AddonModFolderIndexComponent extends CoreCourseModuleMainResourceCo
folder.contents = this.module.contents;
}
this.module = folder;
this.moduleContents = folder.contents;
return folder;
});
}
return promise.then((folder) => {
if (folder) {
this.description = folder.intro || folder.description;
this.dataRetrieved.emit(folder);
}
this.showModuleData(folder);
// All data obtained, now fill the context menu.

View File

@ -19,6 +19,7 @@ import { CoreUtilsProvider } from '@providers/utils/utils';
import { CoreCourseProvider } from '@core/course/providers/course';
import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper';
import { CoreSite } from '@classes/site';
import { CoreWSExternalWarning, CoreWSExternalFile } from '@providers/ws';
/**
* Service that provides some features for folder.
@ -43,7 +44,7 @@ export class AddonModFolderProvider {
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved when the book is retrieved.
*/
getFolder(courseId: number, cmId: number, siteId?: string): Promise<any> {
getFolder(courseId: number, cmId: number, siteId?: string): Promise<AddonModFolderFolder> {
return this.getFolderByKey(courseId, 'coursemodule', cmId, siteId);
}
@ -56,7 +57,7 @@ export class AddonModFolderProvider {
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved when the book is retrieved.
*/
protected getFolderByKey(courseId: number, key: string, value: any, siteId?: string): Promise<any> {
protected getFolderByKey(courseId: number, key: string, value: any, siteId?: string): Promise<AddonModFolderFolder> {
return this.sitesProvider.getSite(siteId).then((site) => {
const params = {
courseids: [courseId]
@ -66,7 +67,9 @@ export class AddonModFolderProvider {
updateFrequency: CoreSite.FREQUENCY_RARELY
};
return site.read('mod_folder_get_folders_by_courses', params, preSets).then((response) => {
return site.read('mod_folder_get_folders_by_courses', params, preSets)
.then((response: AddonModFolderGetFoldersByCoursesResult): any => {
if (response && response.folders) {
const currentFolder = response.folders.find((folder) => {
return folder[key] == value;
@ -147,3 +150,33 @@ export class AddonModFolderProvider {
{}, siteId);
}
}
/**
* Folder returned by mod_folder_get_folders_by_courses.
*/
export type AddonModFolderFolder = {
id: number; // Module id.
coursemodule: number; // Course module id.
course: number; // Course id.
name: string; // Page name.
intro: string; // Summary.
introformat: number; // Intro format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
introfiles: CoreWSExternalFile[];
revision: number; // Incremented when after each file changes, to avoid cache.
timemodified: number; // Last time the folder was modified.
display: number; // Display type of folder contents on a separate page or inline.
showexpanded: number; // 1 = expanded, 0 = collapsed for sub-folders.
showdownloadfolder: number; // Whether to show the download folder button.
section: number; // Course section id.
visible: number; // Module visibility.
groupmode: number; // Group mode.
groupingid: number; // Grouping id.
};
/**
* Result of WS mod_folder_get_folders_by_courses.
*/
export type AddonModFolderGetFoldersByCoursesResult = {
folders: AddonModFolderFolder[];
warnings?: CoreWSExternalWarning[];
};

View File

@ -79,7 +79,7 @@ export class AddonModImscpIndexComponent extends CoreCourseModuleMainResourceCom
const promises = [];
promises.push(this.imscpProvider.getImscp(this.courseId, this.module.id).then((imscp) => {
this.description = imscp.intro || imscp.description;
this.description = imscp.intro;
this.dataRetrieved.emit(imscp);
}));

View File

@ -21,6 +21,7 @@ import { CoreUtilsProvider } from '@providers/utils/utils';
import { CoreCourseProvider } from '@core/course/providers/course';
import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper';
import { CoreSite } from '@classes/site';
import { CoreWSExternalWarning, CoreWSExternalFile } from '@providers/ws';
/**
* Service that provides some features for IMSCP.
@ -157,7 +158,7 @@ export class AddonModImscpProvider {
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved when the imscp is retrieved.
*/
protected getImscpByKey(courseId: number, key: string, value: any, siteId?: string): Promise<any> {
protected getImscpByKey(courseId: number, key: string, value: any, siteId?: string): Promise<AddonModImscpImscp> {
return this.sitesProvider.getSite(siteId).then((site) => {
const params = {
courseids: [courseId]
@ -167,7 +168,9 @@ export class AddonModImscpProvider {
updateFrequency: CoreSite.FREQUENCY_RARELY
};
return site.read('mod_imscp_get_imscps_by_courses', params, preSets).then((response) => {
return site.read('mod_imscp_get_imscps_by_courses', params, preSets)
.then((response: AddonModImscpGetImscpsByCoursesResult): any => {
if (response && response.imscps) {
const currentImscp = response.imscps.find((imscp) => imscp[key] == value);
if (currentImscp) {
@ -188,7 +191,7 @@ export class AddonModImscpProvider {
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved when the imscp is retrieved.
*/
getImscp(courseId: number, cmId: number, siteId?: string): Promise<any> {
getImscp(courseId: number, cmId: number, siteId?: string): Promise<AddonModImscpImscp> {
return this.getImscpByKey(courseId, 'coursemodule', cmId, siteId);
}
@ -324,3 +327,32 @@ export class AddonModImscpProvider {
siteId);
}
}
/**
* IMSCP returned by mod_imscp_get_imscps_by_courses.
*/
export type AddonModImscpImscp = {
id: number; // IMSCP id.
coursemodule: number; // Course module id.
course: number; // Course id.
name: string; // Activity name.
intro?: string; // The IMSCP intro.
introformat?: number; // Intro format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
introfiles?: CoreWSExternalFile[]; // @since 3.2.
revision?: number; // Revision.
keepold?: number; // Number of old IMSCP to keep.
structure?: string; // IMSCP structure.
timemodified?: string; // Time of last modification.
section?: number; // Course section id.
visible?: boolean; // If visible.
groupmode?: number; // Group mode.
groupingid?: number; // Group id.
};
/**
* Result of WS mod_imscp_get_imscps_by_courses.
*/
export type AddonModImscpGetImscpsByCoursesResult = {
imscps: AddonModImscpImscp[];
warnings?: CoreWSExternalWarning[];
};

View File

@ -17,6 +17,7 @@ import { CoreSitesProvider } from '@providers/sites';
import { CoreUtilsProvider } from '@providers/utils/utils';
import { CoreFilepoolProvider } from '@providers/filepool';
import { CoreSite, CoreSiteWSPreSets } from '@classes/site';
import { CoreWSExternalWarning, CoreWSExternalFile } from '@providers/ws';
/**
* Service that provides some features for labels.
@ -52,7 +53,7 @@ export class AddonModLabelProvider {
* @return Promise resolved when the label is retrieved.
*/
protected getLabelByField(courseId: number, key: string, value: any, forceCache?: boolean, ignoreCache?: boolean,
siteId?: string): Promise<any> {
siteId?: string): Promise<AddonModLabelLabel> {
return this.sitesProvider.getSite(siteId).then((site) => {
const params = {
@ -70,7 +71,9 @@ export class AddonModLabelProvider {
preSets.emergencyCache = false;
}
return site.read('mod_label_get_labels_by_courses', params, preSets).then((response) => {
return site.read('mod_label_get_labels_by_courses', params, preSets)
.then((response: AddonModLabelGetLabelsByCoursesResult): any => {
if (response && response.labels) {
const currentLabel = response.labels.find((label) => label[key] == value);
if (currentLabel) {
@ -93,7 +96,8 @@ export class AddonModLabelProvider {
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved when the label is retrieved.
*/
getLabel(courseId: number, cmId: number, forceCache?: boolean, ignoreCache?: boolean, siteId?: string): Promise<any> {
getLabel(courseId: number, cmId: number, forceCache?: boolean, ignoreCache?: boolean, siteId?: string)
: Promise<AddonModLabelLabel> {
return this.getLabelByField(courseId, 'coursemodule', cmId, forceCache, ignoreCache, siteId);
}
@ -107,7 +111,8 @@ export class AddonModLabelProvider {
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved when the label is retrieved.
*/
getLabelById(courseId: number, labelId: number, forceCache?: boolean, ignoreCache?: boolean, siteId?: string): Promise<any> {
getLabelById(courseId: number, labelId: number, forceCache?: boolean, ignoreCache?: boolean, siteId?: string)
: Promise<AddonModLabelLabel> {
return this.getLabelByField(courseId, 'id', labelId, forceCache, ignoreCache, siteId);
}
@ -170,3 +175,29 @@ export class AddonModLabelProvider {
return site.wsAvailable('mod_label_get_labels_by_courses');
}
}
/**
* Label returned by mod_label_get_labels_by_courses.
*/
export type AddonModLabelLabel = {
id: number; // Module id.
coursemodule: number; // Course module id.
course: number; // Course id.
name: string; // Label name.
intro: string; // Label contents.
introformat: number; // Intro format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
introfiles: CoreWSExternalFile[];
timemodified: number; // Last time the label was modified.
section: number; // Course section id.
visible: number; // Module visibility.
groupmode: number; // Group mode.
groupingid: number; // Grouping id.
};
/**
* Result of WS mod_label_get_labels_by_courses.
*/
export type AddonModLabelGetLabelsByCoursesResult = {
labels: AddonModLabelLabel[];
warnings?: CoreWSExternalWarning[];
};

View File

@ -15,7 +15,7 @@
import { Component, Optional, Injector } from '@angular/core';
import { Content } from 'ionic-angular';
import { CoreCourseModuleMainActivityComponent } from '@core/course/classes/main-activity-component';
import { AddonModLtiProvider } from '../../providers/lti';
import { AddonModLtiProvider, AddonModLtiLti } from '../../providers/lti';
/**
* Component that displays an LTI entry page.
@ -28,7 +28,7 @@ export class AddonModLtiIndexComponent extends CoreCourseModuleMainActivityCompo
component = AddonModLtiProvider.COMPONENT;
moduleName = 'lti';
lti: any; // The LTI object.
lti: AddonModLtiLti; // The LTI object.
protected fetchContentDefaultError = 'addon.mod_lti.errorgetlti';
@ -65,7 +65,7 @@ export class AddonModLtiIndexComponent extends CoreCourseModuleMainActivityCompo
protected fetchContent(refresh: boolean = false, sync: boolean = false, showErrors: boolean = false): Promise<any> {
return this.ltiProvider.getLti(this.courseId, this.module.id).then((ltiData) => {
this.lti = ltiData;
this.description = this.lti.intro || this.description;
this.description = this.lti.intro;
this.dataRetrieved.emit(this.lti);
}).then(() => {
// All data obtained, now fill the context menu.

View File

@ -22,11 +22,7 @@ import { CoreUtilsProvider } from '@providers/utils/utils';
import { CoreUrlUtilsProvider } from '@providers/utils/url';
import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper';
import { CoreSite } from '@classes/site';
export interface AddonModLtiParam {
name: string;
value: string;
}
import { CoreWSExternalWarning, CoreWSExternalFile } from '@providers/ws';
/**
* Service that provides some features for LTI.
@ -104,7 +100,7 @@ export class AddonModLtiProvider {
* @param cmId Course module ID.
* @return Promise resolved when the LTI is retrieved.
*/
getLti(courseId: number, cmId: number): Promise<any> {
getLti(courseId: number, cmId: number): Promise<AddonModLtiLti> {
const params: any = {
courseids: [courseId]
};
@ -113,7 +109,9 @@ export class AddonModLtiProvider {
updateFrequency: CoreSite.FREQUENCY_RARELY
};
return this.sitesProvider.getCurrentSite().read('mod_lti_get_ltis_by_courses', params, preSets).then((response) => {
return this.sitesProvider.getCurrentSite().read('mod_lti_get_ltis_by_courses', params, preSets)
.then((response: AddonModLtiGetLtisByCoursesResult): any => {
if (response.ltis) {
const currentLti = response.ltis.find((lti) => lti.coursemodule == cmId);
if (currentLti) {
@ -141,8 +139,8 @@ export class AddonModLtiProvider {
* @param id LTI id.
* @return Promise resolved when the launch data is retrieved.
*/
getLtiLaunchData(id: number): Promise<any> {
const params: any = {
getLtiLaunchData(id: number): Promise<AddonModLtiGetToolLaunchDataResult> {
const params = {
toolid: id
};
@ -154,7 +152,9 @@ export class AddonModLtiProvider {
cacheKey: this.getLtiLaunchDataCacheKey(id)
};
return this.sitesProvider.getCurrentSite().read('mod_lti_get_tool_launch_data', params, preSets).then((response) => {
return this.sitesProvider.getCurrentSite().read('mod_lti_get_tool_launch_data', params, preSets)
.then((response: AddonModLtiGetToolLaunchDataResult): any => {
if (response.endpoint) {
return response;
}
@ -227,3 +227,66 @@ export class AddonModLtiProvider {
return this.logHelper.logSingle('mod_lti_view_lti', params, AddonModLtiProvider.COMPONENT, id, name, 'lti', {}, siteId);
}
}
/**
* LTI returned by mod_lti_get_ltis_by_courses.
*/
export type AddonModLtiLti = {
id: number; // External tool id.
coursemodule: number; // Course module id.
course: number; // Course id.
name: string; // LTI name.
intro?: string; // The LTI intro.
introformat?: number; // Intro format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
introfiles?: CoreWSExternalFile[]; // @since 3.2.
timecreated?: number; // Time of creation.
timemodified?: number; // Time of last modification.
typeid?: number; // Type id.
toolurl?: string; // Tool url.
securetoolurl?: string; // Secure tool url.
instructorchoicesendname?: string; // Instructor choice send name.
instructorchoicesendemailaddr?: number; // Instructor choice send mail address.
instructorchoiceallowroster?: number; // Instructor choice allow roster.
instructorchoiceallowsetting?: number; // Instructor choice allow setting.
instructorcustomparameters?: string; // Instructor custom parameters.
instructorchoiceacceptgrades?: number; // Instructor choice accept grades.
grade?: number; // Enable grades.
launchcontainer?: number; // Launch container mode.
resourcekey?: string; // Resource key.
password?: string; // Shared secret.
debuglaunch?: number; // Debug launch.
showtitlelaunch?: number; // Show title launch.
showdescriptionlaunch?: number; // Show description launch.
servicesalt?: string; // Service salt.
icon?: string; // Alternative icon URL.
secureicon?: string; // Secure icon URL.
section?: number; // Course section id.
visible?: number; // Visible.
groupmode?: number; // Group mode.
groupingid?: number; // Group id.
};
/**
* Param to send to the LTI.
*/
export type AddonModLtiParam = {
name: string; // Parameter name.
value: string; // Parameter value.
};
/**
* Result of WS mod_lti_get_ltis_by_courses.
*/
export type AddonModLtiGetLtisByCoursesResult = {
ltis: AddonModLtiLti[];
warnings?: CoreWSExternalWarning[];
};
/**
* Result of WS mod_lti_get_tool_launch_data.
*/
export type AddonModLtiGetToolLaunchDataResult = {
endpoint: string; // Endpoint URL.
parameters: AddonModLtiParam[];
warnings?: CoreWSExternalWarning[];
};

Some files were not shown because too many files have changed in this diff Show More