From 49e04914284354ec4062aeafe5d8d5e856e839e2 Mon Sep 17 00:00:00 2001 From: Noel De Martin <noel@moodle.com> Date: Mon, 14 Nov 2022 17:52:47 +0100 Subject: [PATCH] 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. --- .../features/grades/pages/course/course.html | 43 +++++----- .../grades/pages/course/course.page.ts | 17 +++- .../features/grades/pages/course/course.scss | 8 ++ .../features/grades/services/grades-helper.ts | 81 +++++++++++++++---- 4 files changed, 114 insertions(+), 35 deletions(-) diff --git a/src/core/features/grades/pages/course/course.html b/src/core/features/grades/pages/course/course.html index bf4835c89..3919f3926 100644 --- a/src/core/features/grades/pages/course/course.html +++ b/src/core/features/grades/pages/course/course.html @@ -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> diff --git a/src/core/features/grades/pages/course/course.page.ts b/src/core/features/grades/pages/course/course.page.ts index f3d57d541..2f85483de 100644 --- a/src/core/features/grades/pages/course/course.page.ts +++ b/src/core/features/grades/pages/course/course.page.ts @@ -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; } diff --git a/src/core/features/grades/pages/course/course.scss b/src/core/features/grades/pages/course/course.scss index 5358730a6..096eb096d 100644 --- a/src/core/features/grades/pages/course/course.scss +++ b/src/core/features/grades/pages/course/course.scss @@ -124,6 +124,14 @@ } } + .core-grades-table-leader { + width: 0; + } + + .ion-no-border { + border: 0 !important; + } + .dimmed_text, .hidden { opacity: .7; diff --git a/src/core/features/grades/services/grades-helper.ts b/src/core/features/grades/services/grades-helper.ts index f417a20a3..720ae5c45 100644 --- a/src/core/features/grades/services/grades-helper.ts +++ b/src/core/features/grades/services/grades-helper.ts @@ -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; } /**