MOBILE-4176 grades: Fix 4.1 layout

Fixes some breaking changes introduced in MDL-75513. The fixes included here are not exhaustive but should take care of the basic scenarios covered by e2e tests. Subsequent fixes will be provided to handle other edge-cases.
main
Noel De Martin 2022-11-14 17:52:47 +01:00
parent d0e3ad6ee8
commit 49e0491428
4 changed files with 114 additions and 35 deletions

View File

@ -27,13 +27,18 @@
</thead> </thead>
<tbody> <tbody>
<ng-container *ngFor="let row of rows"> <ng-container *ngFor="let row of rows">
<tr *ngIf="!useLegacyLayout && row.itemtype === 'leader'">
<td [attr.rowspan]="row.rowspan" class="core-grades-table-leader"></td>
</tr>
<tr [attr.role]="row.expandable && showSummary ? 'button row' : 'row'" <tr [attr.role]="row.expandable && showSummary ? 'button row' : 'row'"
[attr.tabindex]="row.expandable && showSummary && 0" [attr.aria-expanded]="row.expanded" [attr.tabindex]="row.expandable && showSummary && 0" [attr.aria-expanded]="row.expanded"
[attr.aria-label]="rowAriaLabel(row)" [attr.aria-controls]="row.detailsid" [attr.aria-label]="rowAriaLabel(row)" [attr.aria-controls]="row.detailsid"
(ariaButtonClick)="row.expandable && showSummary && toggleRow(row)" [class]="row.rowclass" (ariaButtonClick)="row.expandable && showSummary && toggleRow(row)" [class]="row.rowclass"
[class.core-grades-grade-clickable]="row.expandable && showSummary" [id]="'grade-'+row.id"> [class.core-grades-grade-clickable]="row.expandable && showSummary" [id]="'grade-'+row.id"
*ngIf="useLegacyLayout || row.itemtype !== 'leader'">
<ng-container *ngIf="row.itemtype"> <ng-container *ngIf="row.itemtype">
<td *ngIf="row.itemtype == 'category'" class="core-grades-table-category" [attr.rowspan]="row.rowspan"> <td *ngIf="!useLegacyLayout && row.itemtype == 'category'" class="core-grades-table-category"
[attr.rowspan]="row.rowspan">
</td> </td>
<th class="core-grades-table-gradeitem ion-text-start" [attr.colspan]="row.colspan"> <th class="core-grades-table-gradeitem ion-text-start" [attr.colspan]="row.colspan">
<ion-icon *ngIf="row.expandable && showSummary" aria-hidden="true" slot="start" name="fas-chevron-right" <ion-icon *ngIf="row.expandable && showSummary" aria-hidden="true" slot="start" name="fas-chevron-right"
@ -48,6 +53,7 @@
</core-mod-icon> </core-mod-icon>
<span [innerHTML]="row.gradeitem"></span> <span [innerHTML]="row.gradeitem"></span>
</th> </th>
<ng-container *ngIf="row.itemtype !== 'category'">
<ng-container *ngFor="let column of columns"> <ng-container *ngFor="let column of columns">
<td *ngIf="column.name !== 'gradeitem' && column.name !== 'feedback' && column.name !== 'grade' && <td *ngIf="column.name !== 'gradeitem' && column.name !== 'feedback' && column.name !== 'grade' &&
row[column.name] != undefined" [class]="'ion-text-start core-grades-table-' + column.name" row[column.name] != undefined" [class]="'ion-text-start core-grades-table-' + column.name"
@ -67,6 +73,7 @@
</td> </td>
</ng-container> </ng-container>
</ng-container> </ng-container>
</ng-container>
</tr> </tr>
<tr *ngIf="row.expandable" [id]="row.detailsid" [class]="row.rowclass" [hidden]="!row.expanded"> <tr *ngIf="row.expandable" [id]="row.detailsid" [class]="row.rowclass" [hidden]="!row.expanded">
<td [attr.colspan]="totalColumnsSpan"> <td [attr.colspan]="totalColumnsSpan">

View File

@ -54,6 +54,7 @@ export class CoreGradesCoursePage implements AfterViewInit, OnDestroy {
rows?: CoreGradesFormattedTableRow[]; rows?: CoreGradesFormattedTableRow[];
totalColumnsSpan?: number; totalColumnsSpan?: number;
withinSplitView?: boolean; withinSplitView?: boolean;
useLegacyLayout?: boolean; // Whether to use the layout before 4.1.
protected fetchSuccess = false; protected fetchSuccess = false;
@ -68,6 +69,7 @@ export class CoreGradesCoursePage implements AfterViewInit, OnDestroy {
this.expandLabel = Translate.instant('core.expand'); this.expandLabel = Translate.instant('core.expand');
this.collapseLabel = Translate.instant('core.collapse'); this.collapseLabel = Translate.instant('core.collapse');
this.useLegacyLayout = !CoreSites.getRequiredCurrentSite().isVersionGreaterEqualThan('4.1');
if (route.snapshot.data.swipeEnabled ?? true) { if (route.snapshot.data.swipeEnabled ?? true) {
const source = CoreRoutedItemsManagerSourcesTracker.getOrCreateSource(CoreGradesCoursesSource, []); const source = CoreRoutedItemsManagerSourcesTracker.getOrCreateSource(CoreGradesCoursesSource, []);
@ -133,11 +135,22 @@ export class CoreGradesCoursePage implements AfterViewInit, OnDestroy {
row.expanded = expand ?? !row.expanded; row.expanded = expand ?? !row.expanded;
let colspan: number = this.columns.length + (row.colspan ?? 0) - 1; let colspan: number = this.columns.length + (row.colspan ?? 0);
if (this.useLegacyLayout) {
colspan--;
}
for (let i = this.rows.indexOf(row) - 1; i >= 0; i--) { for (let i = this.rows.indexOf(row) - 1; i >= 0; i--) {
const previousRow = this.rows[i]; const previousRow = this.rows[i];
if (previousRow.expandable || !previousRow.colspan || !previousRow.rowspan || previousRow.colspan !== colspan) { if (
!previousRow.rowspan ||
!previousRow.colspan ||
previousRow.colspan !== colspan ||
(!this.useLegacyLayout && previousRow.itemtype !== 'leader') ||
(this.useLegacyLayout && previousRow.expandable)
) {
continue; continue;
} }

View File

@ -124,6 +124,14 @@
} }
} }
.core-grades-table-leader {
width: 0;
}
.ion-no-border {
border: 0 !important;
}
.dimmed_text, .dimmed_text,
.hidden { .hidden {
opacity: .7; opacity: .7;

View File

@ -25,6 +25,7 @@ import {
CoreGradesTable, CoreGradesTable,
CoreGradesTableColumn, CoreGradesTableColumn,
CoreGradesTableItemNameColumn, CoreGradesTableItemNameColumn,
CoreGradesTableLeaderColumn,
CoreGradesTableRow, CoreGradesTableRow,
} from '@features/grades/services/grades'; } from '@features/grades/services/grades';
import { CoreTextUtils } from '@services/utils/text'; import { CoreTextUtils } from '@services/utils/text';
@ -71,7 +72,8 @@ export class CoreGradesHelperProvider {
let content = String(column.content); let content = String(column.content);
if (name == 'itemname') { if (name == 'itemname') {
await this.setRowIcon(row, content); this.setRowIconAndType(row, content);
row.link = this.getModuleLink(content); row.link = this.getModuleLink(content);
row.rowclass += column.class.indexOf('hidden') >= 0 ? ' hidden' : ''; row.rowclass += column.class.indexOf('hidden') >= 0 ? ' hidden' : '';
row.rowclass += column.class.indexOf('dimmed_text') >= 0 ? ' dimmed_text' : ''; row.rowclass += column.class.indexOf('dimmed_text') >= 0 ? ' dimmed_text' : '';
@ -96,10 +98,23 @@ export class CoreGradesHelperProvider {
* Formats a row from the grades table to be rendered in one table. * Formats a row from the grades table to be rendered in one table.
* *
* @param tableRow JSON object representing row of grades table data. * @param tableRow JSON object representing row of grades table data.
* @param useLegacyLayout Whether to use the layout before 4.1.
* @return Formatted row object. * @return Formatted row object.
*/ */
protected formatGradeRowForTable(tableRow: CoreGradesTableRow): CoreGradesFormattedTableRow { protected formatGradeRowForTable(tableRow: CoreGradesTableRow, useLegacyLayout: boolean): CoreGradesFormattedTableRow {
const row: CoreGradesFormattedTableRow = {}; const row: CoreGradesFormattedTableRow = {};
if (!useLegacyLayout && 'leader' in tableRow) {
const row = {
itemtype: 'leader',
rowspan: tableRow.leader?.rowspan,
};
this.setRowEvenOddClass(row, (tableRow.leader as CoreGradesTableLeaderColumn).class);
return row;
}
for (let name in tableRow) { for (let name in tableRow) {
const column: CoreGradesTableColumn = tableRow[name]; const column: CoreGradesTableColumn = tableRow[name];
@ -116,13 +131,13 @@ export class CoreGradesHelperProvider {
row.colspan = itemNameColumn.colspan; row.colspan = itemNameColumn.colspan;
row.rowspan = tableRow.leader?.rowspan || 1; row.rowspan = tableRow.leader?.rowspan || 1;
this.setRowIcon(row, content); this.setRowIconAndType(row, content);
row.rowclass = itemNameColumn.class.indexOf('leveleven') < 0 ? 'odd' : 'even'; this.setRowEvenOddClass(row, itemNameColumn.class);
row.rowclass += itemNameColumn.class.indexOf('hidden') >= 0 ? ' hidden' : ''; row.rowclass += itemNameColumn.class.indexOf('hidden') >= 0 ? ' hidden' : '';
row.rowclass += itemNameColumn.class.indexOf('dimmed_text') >= 0 ? ' dimmed_text' : ''; row.rowclass += itemNameColumn.class.indexOf('dimmed_text') >= 0 ? ' dimmed_text' : '';
content = content.replace(/<\/span>/gi, '\n'); content = content.replace(/<\/span>/gi, '\n');
content = CoreTextUtils.cleanTags(content); content = CoreTextUtils.cleanTags(content, true);
name = 'gradeitem'; name = 'gradeitem';
} else if (name === 'grade') { } else if (name === 'grade') {
// Add the pass/fail class if present. // Add the pass/fail class if present.
@ -202,7 +217,7 @@ export class CoreGradesHelperProvider {
feedback: false, feedback: false,
contributiontocoursetotal: false, contributiontocoursetotal: false,
}; };
formatted.rows = table.tabledata.map(row => this.formatGradeRowForTable(row)); formatted.rows = this.formatGradesTableRows(table.tabledata);
// Get a row with some info. // Get a row with some info.
let normalRow = formatted.rows.find( let normalRow = formatted.rows.find(
@ -234,6 +249,33 @@ export class CoreGradesHelperProvider {
return formatted; return formatted;
} }
/**
* Format table rows.
*
* @param rows Unformatted rows.
* @returns Formatted rows.
*/
protected formatGradesTableRows(rows: CoreGradesTableRow[]): CoreGradesFormattedTableRow[] {
const useLegacyLayout = !CoreSites.getRequiredCurrentSite().isVersionGreaterEqualThan('4.1');
const formattedRows = rows.map(row => this.formatGradeRowForTable(row, useLegacyLayout));
if (!useLegacyLayout) {
for (let index = 0; index < formattedRows.length - 1; index++) {
const row = formattedRows[index];
const previousRow = formattedRows[index - 1] ?? null;
if (row.itemtype !== 'leader') {
continue;
}
row.colspan = previousRow.colspan;
previousRow.rowclass = `${previousRow.rowclass ?? ''} ion-no-border`.trim();
}
}
return formattedRows;
}
/** /**
* Get course data for grades since they only have courseid. * Get course data for grades since they only have courseid.
* *
@ -474,7 +516,7 @@ export class CoreGradesHelperProvider {
// Find href containing "/mod/xxx/xxx.php". // Find href containing "/mod/xxx/xxx.php".
const regex = /href="([^"]*\/mod\/[^"|^/]*\/[^"|^.]*\.php[^"]*)/; const regex = /href="([^"]*\/mod\/[^"|^/]*\/[^"|^.]*\.php[^"]*)/;
return table.tabledata.filter((row) => { return this.formatGradesTableRows(table.tabledata.filter((row) => {
if (row.itemname && row.itemname.content) { if (row.itemname && row.itemname.content) {
const matches = row.itemname.content.match(regex); const matches = row.itemname.content.match(regex);
@ -486,7 +528,7 @@ export class CoreGradesHelperProvider {
} }
return false; return false;
}).map((row) => this.formatGradeRowForTable(row)); }));
} }
/** /**
@ -582,14 +624,25 @@ export class CoreGradesHelperProvider {
return CoreGrades.invalidateCourseGradesItemsData(courseId, userId, groupId, siteId); return CoreGrades.invalidateCourseGradesItemsData(courseId, userId, groupId, siteId);
} }
/**
* Set 'odd' or 'even' classes into a row.
*
* @param row Row.
* @param classes Existing row classes.
*/
protected setRowEvenOddClass(row: CoreGradesFormattedTableRow, classes: string): void {
const level = parseInt(classes.match(/(?:^|\s)level(\d+)(?:$|\s)/)?.[1] ?? '0');
row.rowclass = `${row.rowclass ?? ''} ${level % 2 === 0 ? 'even' : 'odd'}`.trim();
}
/** /**
* Parses the image and sets it to the row. * Parses the image and sets it to the row.
* *
* @param row Formatted grade row object. * @param row Row.
* @param text HTML where the image will be rendered. * @param text Row content.
* @return Row object with the image.
*/ */
protected setRowIcon<T extends CoreGradesFormattedRowCommonData>(row: T, text: string): T { protected setRowIconAndType(row: CoreGradesFormattedRowCommonData, text: string): void {
text = text.replace('%2F', '/').replace('%2f', '/'); text = text.replace('%2F', '/').replace('%2f', '/');
if (text.indexOf('/agg_mean') > -1) { if (text.indexOf('/agg_mean') > -1) {
row.itemtype = 'agg_mean'; row.itemtype = 'agg_mean';
@ -603,7 +656,7 @@ export class CoreGradesHelperProvider {
row.itemtype = 'outcome'; row.itemtype = 'outcome';
row.icon = 'fas-tasks'; row.icon = 'fas-tasks';
row.iconAlt = Translate.instant('core.grades.outcome'); row.iconAlt = Translate.instant('core.grades.outcome');
} else if (text.indexOf('i/folder') > -1 || text.indexOf('fa-folder') > -1) { } else if (text.indexOf('i/folder') > -1 || text.indexOf('fa-folder') > -1 || text.indexOf('category-content') > -1) {
row.itemtype = 'category'; row.itemtype = 'category';
row.icon = 'fas-folder'; row.icon = 'fas-folder';
row.iconAlt = Translate.instant('core.grades.category'); row.iconAlt = Translate.instant('core.grades.category');
@ -643,8 +696,6 @@ export class CoreGradesHelperProvider {
row.iconAlt = Translate.instant('core.unknown'); row.iconAlt = Translate.instant('core.unknown');
} }
} }
return row;
} }
/** /**