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>
<tbody>
<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'"
[attr.tabindex]="row.expandable && showSummary && 0" [attr.aria-expanded]="row.expanded"
[attr.aria-label]="rowAriaLabel(row)" [attr.aria-controls]="row.detailsid"
(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">
<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>
<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"
@ -48,23 +53,25 @@
</core-mod-icon>
<span [innerHTML]="row.gradeitem"></span>
</th>
<ng-container *ngFor="let column of columns">
<td *ngIf="column.name !== 'gradeitem' && column.name !== 'feedback' && column.name !== 'grade' &&
<ng-container *ngIf="row.itemtype !== 'category'">
<ng-container *ngFor="let column of columns">
<td *ngIf="column.name !== 'gradeitem' && column.name !== 'feedback' && column.name !== 'grade' &&
row[column.name] != undefined" [class]="'ion-text-start core-grades-table-' + column.name"
[class.ion-hide-md-down]="column.hiddenPhone" [innerHTML]="row[column.name]">
</td>
<td *ngIf="column.name === 'feedback' && row.feedback !== undefined"
class="ion-text-start core-grades-table-feedback" [class.ion-hide-md-down]="column.hiddenPhone">
<core-format-text collapsible-item [text]="row.feedback" contextLevel="course"
[contextInstanceId]="courseId">
</core-format-text>
</td>
<td *ngIf="column.name === 'grade'" [class.ion-hide-md-down]="column.hiddenPhone"
class="ion-text-start core-grades-table-grade {{row.gradeClass}}">
<ion-icon *ngIf="row.gradeIcon" [name]="row.gradeIcon" [attr.aria-label]="row.gradeIconAlt">
</ion-icon>
<span [innerHTML]="row[column.name]"></span>
</td>
[class.ion-hide-md-down]="column.hiddenPhone" [innerHTML]="row[column.name]">
</td>
<td *ngIf="column.name === 'feedback' && row.feedback !== undefined"
class="ion-text-start core-grades-table-feedback" [class.ion-hide-md-down]="column.hiddenPhone">
<core-format-text collapsible-item [text]="row.feedback" contextLevel="course"
[contextInstanceId]="courseId">
</core-format-text>
</td>
<td *ngIf="column.name === 'grade'" [class.ion-hide-md-down]="column.hiddenPhone"
class="ion-text-start core-grades-table-grade {{row.gradeClass}}">
<ion-icon *ngIf="row.gradeIcon" [name]="row.gradeIcon" [attr.aria-label]="row.gradeIconAlt">
</ion-icon>
<span [innerHTML]="row[column.name]"></span>
</td>
</ng-container>
</ng-container>
</ng-container>
</tr>

View File

@ -54,6 +54,7 @@ export class CoreGradesCoursePage implements AfterViewInit, OnDestroy {
rows?: CoreGradesFormattedTableRow[];
totalColumnsSpan?: number;
withinSplitView?: boolean;
useLegacyLayout?: boolean; // Whether to use the layout before 4.1.
protected fetchSuccess = false;
@ -68,6 +69,7 @@ export class CoreGradesCoursePage implements AfterViewInit, OnDestroy {
this.expandLabel = Translate.instant('core.expand');
this.collapseLabel = Translate.instant('core.collapse');
this.useLegacyLayout = !CoreSites.getRequiredCurrentSite().isVersionGreaterEqualThan('4.1');
if (route.snapshot.data.swipeEnabled ?? true) {
const source = CoreRoutedItemsManagerSourcesTracker.getOrCreateSource(CoreGradesCoursesSource, []);
@ -133,11 +135,22 @@ export class CoreGradesCoursePage implements AfterViewInit, OnDestroy {
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--) {
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;
}

View File

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

View File

@ -25,6 +25,7 @@ import {
CoreGradesTable,
CoreGradesTableColumn,
CoreGradesTableItemNameColumn,
CoreGradesTableLeaderColumn,
CoreGradesTableRow,
} from '@features/grades/services/grades';
import { CoreTextUtils } from '@services/utils/text';
@ -71,7 +72,8 @@ export class CoreGradesHelperProvider {
let content = String(column.content);
if (name == 'itemname') {
await this.setRowIcon(row, content);
this.setRowIconAndType(row, content);
row.link = this.getModuleLink(content);
row.rowclass += column.class.indexOf('hidden') >= 0 ? ' hidden' : '';
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.
*
* @param tableRow JSON object representing row of grades table data.
* @param useLegacyLayout Whether to use the layout before 4.1.
* @return Formatted row object.
*/
protected formatGradeRowForTable(tableRow: CoreGradesTableRow): CoreGradesFormattedTableRow {
protected formatGradeRowForTable(tableRow: CoreGradesTableRow, useLegacyLayout: boolean): 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) {
const column: CoreGradesTableColumn = tableRow[name];
@ -116,13 +131,13 @@ export class CoreGradesHelperProvider {
row.colspan = itemNameColumn.colspan;
row.rowspan = tableRow.leader?.rowspan || 1;
this.setRowIcon(row, content);
row.rowclass = itemNameColumn.class.indexOf('leveleven') < 0 ? 'odd' : 'even';
this.setRowIconAndType(row, content);
this.setRowEvenOddClass(row, itemNameColumn.class);
row.rowclass += itemNameColumn.class.indexOf('hidden') >= 0 ? ' hidden' : '';
row.rowclass += itemNameColumn.class.indexOf('dimmed_text') >= 0 ? ' dimmed_text' : '';
content = content.replace(/<\/span>/gi, '\n');
content = CoreTextUtils.cleanTags(content);
content = CoreTextUtils.cleanTags(content, true);
name = 'gradeitem';
} else if (name === 'grade') {
// Add the pass/fail class if present.
@ -202,7 +217,7 @@ export class CoreGradesHelperProvider {
feedback: false,
contributiontocoursetotal: false,
};
formatted.rows = table.tabledata.map(row => this.formatGradeRowForTable(row));
formatted.rows = this.formatGradesTableRows(table.tabledata);
// Get a row with some info.
let normalRow = formatted.rows.find(
@ -234,6 +249,33 @@ export class CoreGradesHelperProvider {
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.
*
@ -474,7 +516,7 @@ export class CoreGradesHelperProvider {
// Find href containing "/mod/xxx/xxx.php".
const regex = /href="([^"]*\/mod\/[^"|^/]*\/[^"|^.]*\.php[^"]*)/;
return table.tabledata.filter((row) => {
return this.formatGradesTableRows(table.tabledata.filter((row) => {
if (row.itemname && row.itemname.content) {
const matches = row.itemname.content.match(regex);
@ -486,7 +528,7 @@ export class CoreGradesHelperProvider {
}
return false;
}).map((row) => this.formatGradeRowForTable(row));
}));
}
/**
@ -582,14 +624,25 @@ export class CoreGradesHelperProvider {
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.
*
* @param row Formatted grade row object.
* @param text HTML where the image will be rendered.
* @return Row object with the image.
* @param row Row.
* @param text Row content.
*/
protected setRowIcon<T extends CoreGradesFormattedRowCommonData>(row: T, text: string): T {
protected setRowIconAndType(row: CoreGradesFormattedRowCommonData, text: string): void {
text = text.replace('%2F', '/').replace('%2f', '/');
if (text.indexOf('/agg_mean') > -1) {
row.itemtype = 'agg_mean';
@ -603,7 +656,7 @@ export class CoreGradesHelperProvider {
row.itemtype = 'outcome';
row.icon = 'fas-tasks';
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.icon = 'fas-folder';
row.iconAlt = Translate.instant('core.grades.category');
@ -643,8 +696,6 @@ export class CoreGradesHelperProvider {
row.iconAlt = Translate.instant('core.unknown');
}
}
return row;
}
/**