MOBILE-3931 module: Add grades and course to module summary
parent
71788e83bf
commit
6a862730e2
|
@ -1741,6 +1741,7 @@
|
|||
"core.grades.fail": "grades",
|
||||
"core.grades.feedback": "grades",
|
||||
"core.grades.grade": "grades",
|
||||
"core.grades.gradebook": "grades",
|
||||
"core.grades.gradeitem": "grades",
|
||||
"core.grades.gradepass": "grades",
|
||||
"core.grades.grades": "grades",
|
||||
|
|
|
@ -24,6 +24,17 @@
|
|||
<ion-icon name="fas-external-link-alt" slot="icon-only" aria-hidden="true"></ion-icon>
|
||||
</ion-button>
|
||||
</ion-item>
|
||||
|
||||
<ion-item class="ion-text-wrap" *ngIf="course" (click)="openCourse()" button [detail]="true">
|
||||
<ion-label>
|
||||
<p class="item-heading">{{ 'core.course' | translate}}</p>
|
||||
<p>
|
||||
<core-format-text [text]="course.displayname || course.fullname" contextLevel="course" [contextInstanceId]="courseId">
|
||||
</core-format-text>
|
||||
</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-item class="ion-text-wrap" *ngIf="module && description && displayOptions.displayDescription">
|
||||
<ion-label>
|
||||
<core-format-text [text]="description" [component]="component" [componentId]="componentId" contextLevel="module"
|
||||
|
@ -32,7 +43,7 @@
|
|||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-item button class="ion-margin" *ngIf="prefetchText && displayOptions.displayPrefetch" class="ion-text-wrap">
|
||||
<ion-item button *ngIf="prefetchText && displayOptions.displayPrefetch" class="ion-text-wrap">
|
||||
<ion-label>
|
||||
<p class="item-heading ion-text-wrap">{{ prefetchText }}</p>
|
||||
<p *ngIf="downloadTimeReadable">{{ downloadTimeReadable }}</p>
|
||||
|
@ -45,7 +56,7 @@
|
|||
<ion-spinner *ngIf="prefetchStatusIcon == 'spinner'" slot="end" aria-hidden="true"></ion-spinner>
|
||||
</ion-item>
|
||||
|
||||
<ion-item button class="ion-margin" *ngIf="sizeReadable && displayOptions.displaySize" class="ion-text-wrap">
|
||||
<ion-item button *ngIf="sizeReadable && displayOptions.displaySize" class="ion-text-wrap">
|
||||
<ion-label>
|
||||
<p class="item-heading ion-text-wrap">{{ 'addon.storagemanager.totalspaceusage' | translate }}</p>
|
||||
<ion-badge color="light">{{ sizeReadable | coreBytesToSize }}</ion-badge>
|
||||
|
@ -57,7 +68,102 @@
|
|||
<ion-spinner *ngIf="removeFilesLoading" slot="end" aria-hidden="true"></ion-spinner>
|
||||
</ion-item>
|
||||
|
||||
<ion-button *ngIf="blog && displayOptions.displayBlog" class="ion-margin" (click)="gotoBlog()" expand="block" fill="outline">
|
||||
<ion-card *ngIf="displayOptions.displayGrades && grades?.length > 0">
|
||||
<ion-list>
|
||||
<ion-item lines="full" class="ion-text-wrap">
|
||||
<ion-label>
|
||||
<h2>{{ 'core.grades.gradebook' | translate }}</h2>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<ng-container *ngFor="let grade of grades">
|
||||
<ion-item button *ngIf="grade.gradeitem" class="ion-text-wrap divider" (click)="toggleGrade(grade)"
|
||||
[attr.aria-label]="(grade.expanded ? 'core.collapse' : 'core.expand') | translate"
|
||||
[attr.aria-expanded]="grade.expanded" [attr.aria-controls]="'grade-'+grade.id" role="heading" detail="false">
|
||||
<ion-icon name="fas-chevron-right" flip-rtl slot="start" aria-hidden="true" class="expandable-status-icon"
|
||||
[class.expandable-status-icon-expanded]="grade.expanded">
|
||||
</ion-icon>
|
||||
<ion-label>
|
||||
<p class="item-heading" *ngIf="!grade.itemmodule">
|
||||
<core-format-text [text]="grade.gradeitem" contextLevel="course" [contextInstanceId]="courseId">
|
||||
</core-format-text>
|
||||
</p>
|
||||
<p class="item-heading" *ngIf="grade.itemmodule">
|
||||
{{ 'core.grades.grade' | translate}}
|
||||
</p>
|
||||
<p *ngIf="grade.grade" [innerHTML]="grade.grade"></p>
|
||||
</ion-label>
|
||||
<ion-icon *ngIf="grade.icon" name="{{grade.icon}}" slot="end" [attr.aria-label]="grade.iconAlt">
|
||||
</ion-icon>
|
||||
<img *ngIf="grade.image && !grade.itemmodule" [src]="grade.image" slot="end" [alt]="grade.iconAlt" />
|
||||
<ion-icon *ngIf="grade.image && grade.itemmodule" name="fas-chart-bar" slot="end" [attr.aria-label]="grade.iconAlt">
|
||||
</ion-icon>
|
||||
</ion-item>
|
||||
<div *ngIf="grade.expanded" [id]="'grade-'+grade.id">
|
||||
<ion-item class="ion-text-wrap" *ngIf="grade.weight?.length > 0 && grade.weight != '-'">
|
||||
<ion-label>
|
||||
<p class="item-heading">{{ 'core.grades.weight' | translate}}</p>
|
||||
<p [innerHTML]="grade.weight"></p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item class="ion-text-wrap" *ngIf="grade.range?.length > 0 && grade.range != '-'">
|
||||
<ion-label>
|
||||
<p class="item-heading">{{ 'core.grades.range' | translate}}</p>
|
||||
<p [innerHTML]="grade.range"></p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-item class="ion-text-wrap" *ngIf="grade.percentage?.length > 0 && grade.percentage != '-'">
|
||||
<ion-label>
|
||||
<p class="item-heading">{{ 'core.grades.percentage' | translate}}</p>
|
||||
<p [innerHTML]="grade.percentage"></p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-item class="ion-text-wrap" *ngIf="grade.lettergrade?.length > 0 && grade.lettergrade != '-'">
|
||||
<ion-label>
|
||||
<p class="item-heading">{{ 'core.grades.lettergrade' | translate}}</p>
|
||||
<p [innerHTML]="grade.lettergrade"></p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-item class="ion-text-wrap" *ngIf="grade.rank?.length > 0 && grade.rank != '-'">
|
||||
<ion-label>
|
||||
<p class="item-heading">{{ 'core.grades.rank' | translate}}</p>
|
||||
<p [innerHTML]="grade.rank"></p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-item class="ion-text-wrap" *ngIf="grade.average?.length > 0 && grade.average != '-'">
|
||||
<ion-label>
|
||||
<p class="item-heading">{{ 'core.grades.average' | translate}}</p>
|
||||
<p [innerHTML]="grade.average"></p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-item class="ion-text-wrap" *ngIf="grade.feedback?.length > 0 && grade.feedback != '-'">
|
||||
<ion-label>
|
||||
<p class="item-heading">{{ 'core.grades.feedback' | translate}}</p>
|
||||
<p>
|
||||
<core-format-text [maxHeight]="120" [text]="grade.feedback" contextLevel="course"
|
||||
[contextInstanceId]="courseId">
|
||||
</core-format-text>
|
||||
</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-item class="ion-text-wrap"
|
||||
*ngIf="grade.contributiontocoursetotal?.length > 0 && grade.contributiontocoursetotal != '-'">
|
||||
<ion-label>
|
||||
<p class="item-heading">{{ 'core.grades.contributiontocoursetotal' | translate}}</p>
|
||||
<p [innerHTML]="grade.contributiontocoursetotal"></p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</div>
|
||||
</ng-container>
|
||||
</ion-list>
|
||||
</ion-card>
|
||||
|
||||
<ion-button *ngIf="blog && displayOptions.displayBlog" (click)="gotoBlog()" expand="block" fill="outline">
|
||||
<ion-icon name="far-newspaper" slot="start" aria-hidden="true"></ion-icon>
|
||||
<ion-label>
|
||||
{{ 'addon.blog.blog' | translate }}
|
||||
|
|
|
@ -17,8 +17,12 @@ import { AddonBlog } from '@addons/blog/services/blog';
|
|||
import { AddonBlogMainMenuHandlerService } from '@addons/blog/services/handlers/mainmenu';
|
||||
import { Component, Input, OnDestroy, OnInit } from '@angular/core';
|
||||
import { Params } from '@angular/router';
|
||||
import { CoreCourse } from '@features/course/services/course';
|
||||
import { CoreCourseHelper, CoreCourseModuleData } from '@features/course/services/course-helper';
|
||||
import { CoreCourseModuleDelegate } from '@features/course/services/module-delegate';
|
||||
import { CoreCourseModulePrefetchDelegate } from '@features/course/services/module-prefetch-delegate';
|
||||
import { CoreCourses, CoreEnrolledCourseData } from '@features/courses/services/courses';
|
||||
import { CoreGradesFormattedRow, CoreGradesFormattedTableRow, CoreGradesHelper } from '@features/grades/services/grades-helper';
|
||||
import { CoreApp } from '@services/app';
|
||||
import { CoreFilepool } from '@services/filepool';
|
||||
import { CoreNavigator } from '@services/navigator';
|
||||
|
@ -61,13 +65,12 @@ export class CoreCourseModuleSummaryComponent implements OnInit, OnDestroy {
|
|||
sizeReadable?: string;
|
||||
downloadTimeReadable?: string; // Last download time in a readable format.
|
||||
size = 0;
|
||||
|
||||
grades?: CoreGradesFormattedRow[];
|
||||
blog = false; // If blog is available.
|
||||
|
||||
isOnline = false; // If the app is online or not.
|
||||
course?: CoreEnrolledCourseData;
|
||||
|
||||
protected onlineSubscription: Subscription; // It will observe the status of the network connection.
|
||||
|
||||
protected packageStatusObserver?: CoreEventObserver; // Observer of package status.
|
||||
protected fileStatusObserver?: CoreEventObserver; // Observer of file status.
|
||||
protected siteId: string;
|
||||
|
@ -103,8 +106,15 @@ export class CoreCourseModuleSummaryComponent implements OnInit, OnDestroy {
|
|||
displayPrefetch: true,
|
||||
displaySize: true,
|
||||
displayBlog: true,
|
||||
displayGrades: true,
|
||||
}, this.displayOptions);
|
||||
|
||||
this.displayOptions.displayGrades = this.displayOptions.displayGrades &&
|
||||
CoreCourseModuleDelegate.supportsFeature(this.module.modname, CoreConstants.FEATURE_GRADE_HAS_GRADE, true);
|
||||
|
||||
this.displayOptions.displayDescription = this.displayOptions.displayDescription &&
|
||||
CoreCourseModuleDelegate.supportsFeature(this.module.modname, CoreConstants.FEATURE_SHOW_DESCRIPTION, true);
|
||||
|
||||
this.fetchContent();
|
||||
|
||||
if (this.component) {
|
||||
|
@ -164,7 +174,11 @@ export class CoreCourseModuleSummaryComponent implements OnInit, OnDestroy {
|
|||
|
||||
this.blog = await AddonBlog.isPluginEnabled();
|
||||
|
||||
await this.getPackageStatus();
|
||||
await Promise.all([
|
||||
this.getPackageStatus(),
|
||||
this.fetchGrades(),
|
||||
this.fetchCourse(),
|
||||
]);
|
||||
|
||||
this.loaded = true;
|
||||
}
|
||||
|
@ -174,7 +188,7 @@ export class CoreCourseModuleSummaryComponent implements OnInit, OnDestroy {
|
|||
*
|
||||
* @param refresh If prefetch info has to be refreshed.
|
||||
*/
|
||||
async getPackageStatus(refresh = false): Promise<void> {
|
||||
protected async getPackageStatus(refresh = false): Promise<void> {
|
||||
if (!this.module) {
|
||||
return;
|
||||
}
|
||||
|
@ -214,6 +228,50 @@ export class CoreCourseModuleSummaryComponent implements OnInit, OnDestroy {
|
|||
await CoreNavigator.navigateToSitePath(AddonBlogMainMenuHandlerService.PAGE_NAME, { params });
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch grade module info.
|
||||
*/
|
||||
protected async fetchGrades(): Promise<void> {
|
||||
if (!this.displayOptions.displayGrades) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.grades = await CoreGradesHelper.getModuleGrades(this.courseId, this.moduleId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle grades expand.
|
||||
*
|
||||
* @param grade Row to expand.
|
||||
*/
|
||||
toggleGrade(grade: CoreGradesFormattedTableRow): void {
|
||||
grade.expanded = !grade.expanded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch course.
|
||||
*/
|
||||
protected async fetchCourse(): Promise<void> {
|
||||
this.course = await CoreCourses.getUserCourse(this.courseId, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Open course.
|
||||
*/
|
||||
openCourse(): void {
|
||||
if (!this.course) {
|
||||
return;
|
||||
}
|
||||
|
||||
CoreCourse.openCourse(
|
||||
this.course,
|
||||
{
|
||||
replace: true,
|
||||
animationDirection: 'back',
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefetch the module.
|
||||
*/
|
||||
|
@ -327,4 +385,5 @@ export type CoreCourseModuleSummaryDisplayOptions = {
|
|||
displayPrefetch?: boolean;
|
||||
displaySize?: boolean;
|
||||
displayBlog?: boolean;
|
||||
displayGrades?: boolean;
|
||||
};
|
||||
|
|
|
@ -130,9 +130,13 @@ export class CoreCourseFormatDefaultHandler implements CoreCourseFormatHandler {
|
|||
navOptions.params = navOptions.params || {};
|
||||
Object.assign(navOptions.params, { course: course });
|
||||
|
||||
// Don't return the .push promise, we don't want to display a loading modal during the page transition.
|
||||
const currentTab = CoreNavigator.getCurrentMainMenuTab();
|
||||
const routeDepth = CoreNavigator.getRouteDepth(`/main/${currentTab}/course/${course.id}`);
|
||||
// When replace is true, disable route depth.
|
||||
let routeDepth = 0;
|
||||
if (!navOptions.replace) {
|
||||
// Don't return the .push promise, we don't want to display a loading modal during the page transition.
|
||||
const currentTab = CoreNavigator.getCurrentMainMenuTab();
|
||||
routeDepth = CoreNavigator.getRouteDepth(`/main/${currentTab}/course/${course.id}`);
|
||||
}
|
||||
const deepPath = '/deep'.repeat(routeDepth);
|
||||
|
||||
CoreNavigator.navigateToSitePath(`course${deepPath}/${course.id}`, navOptions);
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
"fail": "Fail",
|
||||
"feedback": "Feedback",
|
||||
"grade": "Grade",
|
||||
"gradebook": "Gradebook",
|
||||
"gradeitem": "Grade item",
|
||||
"gradepass": "Grade to pass",
|
||||
"grades": "Grades",
|
||||
|
|
|
@ -457,6 +457,38 @@ export class CoreGradesHelperProvider {
|
|||
}).map((row) => this.formatGradeRow(row)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get module grades to display.
|
||||
*
|
||||
* @param courseId Course Id.
|
||||
* @param moduleId Module Id.
|
||||
* @return Formatted table rows.
|
||||
*/
|
||||
async getModuleGrades(courseId: number, moduleId: number): Promise<CoreGradesFormattedTableRow[] > {
|
||||
const table = await CoreGrades.getCourseGradesTable(courseId);
|
||||
|
||||
if (!table.tabledata) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Find href containing "/mod/xxx/xxx.php".
|
||||
const regex = /href="([^"]*\/mod\/[^"|^/]*\/[^"|^.]*\.php[^"]*)/;
|
||||
|
||||
return await Promise.all(table.tabledata.filter((row) => {
|
||||
if (row.itemname && row.itemname.content) {
|
||||
const matches = row.itemname.content.match(regex);
|
||||
|
||||
if (matches && matches.length) {
|
||||
const hrefParams = CoreUrlUtils.extractUrlParams(matches[1]);
|
||||
|
||||
return hrefParams && parseInt(hrefParams.id) === moduleId;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}).map((row) => this.formatGradeRowForTable(row)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Go to view grades.
|
||||
*
|
||||
|
|
|
@ -90,6 +90,9 @@ export class CoreSitePluginsModuleIndexComponent implements OnInit, OnDestroy, C
|
|||
displayRefresh = true;
|
||||
displayPrefetch = true;
|
||||
displaySize = true;
|
||||
displayGrades = false;
|
||||
// @TODO: // Currently display blogs is not an option since it may change soon adding new summary handlers.
|
||||
displayBlog = false;
|
||||
|
||||
ptrEnabled = true;
|
||||
isDestroyed = false;
|
||||
|
@ -128,6 +131,7 @@ export class CoreSitePluginsModuleIndexComponent implements OnInit, OnDestroy, C
|
|||
this.displayRefresh = !CoreUtils.isFalseOrZero(handlerSchema.displayrefresh);
|
||||
this.displayPrefetch = !CoreUtils.isFalseOrZero(handlerSchema.displayprefetch);
|
||||
this.displaySize = !CoreUtils.isFalseOrZero(handlerSchema.displaysize);
|
||||
this.displayGrades = CoreUtils.isTrueOrOne(handlerSchema.displaygrades); // False by default.
|
||||
this.ptrEnabled = !CoreUtils.isFalseOrZero(handlerSchema.ptrenabled);
|
||||
}
|
||||
|
||||
|
@ -196,7 +200,8 @@ export class CoreSitePluginsModuleIndexComponent implements OnInit, OnDestroy, C
|
|||
displayRefresh: this.displayRefresh,
|
||||
displayPrefetch: this.displayPrefetch,
|
||||
displaySize: this.displaySize,
|
||||
displayBlog: false,
|
||||
displayBlog: this.displayBlog,
|
||||
displayGrades: this.displayGrades,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -871,6 +871,7 @@ export type CoreSitePluginsCourseModuleHandlerData = CoreSitePluginsHandlerCommo
|
|||
displayrefresh?: boolean;
|
||||
displayprefetch?: boolean;
|
||||
displaysize?: boolean;
|
||||
displaygrades?: boolean;
|
||||
coursepagemethod?: string;
|
||||
ptrenabled?: boolean;
|
||||
supportedfeatures?: Record<string, unknown>;
|
||||
|
|
Loading…
Reference in New Issue