diff --git a/scripts/langindex.json b/scripts/langindex.json index af83bf1ca..6899fcdff 100644 --- a/scripts/langindex.json +++ b/scripts/langindex.json @@ -1734,6 +1734,7 @@ "core.grades.calculatedgrade": "grades", "core.grades.category": "grades", "core.grades.contributiontocoursetotal": "grades", + "core.grades.fail": "grades", "core.grades.feedback": "grades", "core.grades.grade": "grades", "core.grades.gradeitem": "grades", @@ -1744,6 +1745,7 @@ "core.grades.nogradesreturned": "grades", "core.grades.nooutcome": "grades", "core.grades.outcome": "grades", + "core.grades.pass": "grades", "core.grades.percentage": "grades", "core.grades.range": "grades", "core.grades.rank": "grades", diff --git a/src/addons/mod/assign/components/submission/submission.ts b/src/addons/mod/assign/components/submission/submission.ts index da2154f88..9d5735936 100644 --- a/src/addons/mod/assign/components/submission/submission.ts +++ b/src/addons/mod/assign/components/submission/submission.ts @@ -196,7 +196,11 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy, Can * @param response Response of get submission status. */ protected calculateTimeRemaining(response: AddonModAssignGetSubmissionStatusWSResponse): void { - if (this.assign!.duedate <= 0) { + if (!this.assign) { + return; + } + + if (this.assign.duedate <= 0) { this.timeRemaining = ''; this.timeRemainingClass = ''; @@ -206,7 +210,7 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy, Can const time = CoreTimeUtils.timestamp(); const dueDate = response.lastattempt?.extensionduedate ? response.lastattempt.extensionduedate - : this.assign!.duedate; + : this.assign.duedate; const timeRemaining = dueDate - time; if (timeRemaining > 0) { @@ -276,6 +280,10 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy, Can * Copy a previous attempt and then go to edit. */ async copyPrevious(): Promise { + if (!this.assign) { + return; + } + if (!CoreApp.isOnline()) { CoreDomUtils.showErrorModal('core.networkerrormsg', true); @@ -291,7 +299,7 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy, Can let modal = await CoreDomUtils.showModalLoading(); const size = await CoreUtils.ignoreErrors( - AddonModAssignHelper.getSubmissionSizeForCopy(this.assign!, previousSubmission), + AddonModAssignHelper.getSubmissionSizeForCopy(this.assign, previousSubmission), -1, ); // Error calculating size, return -1. @@ -309,15 +317,15 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy, Can modal = await CoreDomUtils.showModalLoading('core.sending', true); try { - await AddonModAssignHelper.copyPreviousAttempt(this.assign!, previousSubmission); + await AddonModAssignHelper.copyPreviousAttempt(this.assign, previousSubmission); // Now go to edit. this.goToEdit(); - if (!this.assign!.submissiondrafts) { + if (!this.assign.submissiondrafts && this.userSubmission) { // No drafts allowed, so it was submitted. Trigger event. CoreEvents.trigger(AddonModAssignProvider.SUBMITTED_FOR_GRADING_EVENT, { - assignmentId: this.assign!.id, - submissionId: this.userSubmission!.id, + assignmentId: this.assign.id, + submissionId: this.userSubmission.id, userId: this.currentUserId, }, this.siteId); } else { @@ -337,8 +345,8 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy, Can * @return Promise resolved when done. */ protected async discardDrafts(): Promise { - if (this.feedback && this.feedback.plugins) { - await AddonModAssignHelper.discardFeedbackPluginData(this.assign!.id, this.submitId, this.feedback); + if (this.assign && this.feedback && this.feedback.plugins) { + await AddonModAssignHelper.discardFeedbackPluginData(this.assign.id, this.submitId, this.feedback); } } @@ -363,7 +371,7 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy, Can * @return Promise resolved with boolean: whether there's data to save. */ protected async hasDataToSave(isSubmit = false): Promise { - if (!this.canSaveGrades || !this.loaded) { + if (!this.canSaveGrades || !this.loaded || !this.assign) { return false; } @@ -379,7 +387,7 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy, Can } // Check if outcomes changed. - if (this.gradeInfo && this.gradeInfo.outcomes) { + if (this.gradeInfo?.outcomes) { for (const x in this.gradeInfo.outcomes) { const outcome = this.gradeInfo.outcomes[x]; @@ -396,7 +404,7 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy, Can try { return AddonModAssignHelper.hasFeedbackDataChanged( - this.assign!, + this.assign, this.userSubmission, this.feedback, this.submitId, @@ -435,13 +443,13 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy, Can promises.push(AddonModAssign.invalidateAssignmentData(this.courseId)); if (this.assign) { promises.push(AddonModAssign.invalidateSubmissionStatusData( - this.assign!.id, + this.assign.id, this.submitId, undefined, !!this.blindId, )); - promises.push(AddonModAssign.invalidateAssignmentUserMappingsData(this.assign!.id)); - promises.push(AddonModAssign.invalidateListParticipantsData(this.assign!.id)); + promises.push(AddonModAssign.invalidateAssignmentUserMappingsData(this.assign.id)); + promises.push(AddonModAssign.invalidateListParticipantsData(this.assign.id)); } promises.push(CoreGradesHelper.invalidateGradeModuleItems(this.courseId, this.submitId)); promises.push(CoreCourse.invalidateModule(this.moduleId)); @@ -534,13 +542,13 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy, Can } // Treat last attempt. - promises = this.treatLastAttempt(submissionStatus); + promises = this.treatLastAttempt(submissionStatus, this.lastAttempt); // Calculate the time remaining. this.calculateTimeRemaining(submissionStatus); // Load the feedback. - promises.push(this.loadFeedback(submissionStatus.feedback)); + promises.push(this.loadFeedback(this.assign, submissionStatus.feedback)); // Check if there's any unsupported plugin for editing. if (!this.userSubmission || !this.userSubmission.plugins) { @@ -575,8 +583,12 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy, Can * @return Promise resolved when done. */ protected async loadSubmissionOfflineData(): Promise { + if (!this.assign) { + return; + } + try { - const submission = await AddonModAssignOffline.getSubmission(this.assign!.id, this.submitId); + const submission = await AddonModAssignOffline.getSubmission(this.assign.id, this.submitId); this.hasOffline = submission && submission.plugindata && Object.keys(submission.plugindata).length > 0; @@ -591,10 +603,11 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy, Can /** * Load the data to render the feedback and grade. * + * @param assign Assign data. * @param feedback The feedback data from the submission status. * @return Promise resolved when done. */ - protected async loadFeedback(feedback?: AddonModAssignSubmissionFeedback): Promise { + protected async loadFeedback(assign: AddonModAssignAssign, feedback?: AddonModAssignSubmissionFeedback): Promise { this.grade = { method: '', modified: 0, @@ -654,22 +667,22 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy, Can // Treat the grade info. await this.treatGradeInfo(); - const isManual = this.assign!.attemptreopenmethod == AddonModAssignAttemptReopenMethodValues.MANUAL; - const isUnlimited = this.assign!.maxattempts == AddonModAssignProvider.UNLIMITED_ATTEMPTS; - const isLessThanMaxAttempts = !!this.userSubmission && (this.userSubmission.attemptnumber < (this.assign!.maxattempts - 1)); + const isManual = assign.attemptreopenmethod == AddonModAssignAttemptReopenMethodValues.MANUAL; + const isUnlimited = assign.maxattempts == AddonModAssignProvider.UNLIMITED_ATTEMPTS; + const isLessThanMaxAttempts = !!this.userSubmission && (this.userSubmission.attemptnumber < (assign.maxattempts - 1)); this.allowAddAttempt = isManual && (!this.userSubmission || isUnlimited || isLessThanMaxAttempts); - if (this.assign!.teamsubmission) { + if (assign.teamsubmission) { this.grade.applyToAll = true; this.originalGrades.applyToAll = true; } - if (this.assign!.markingworkflow && this.grade.gradingStatus) { + if (assign.markingworkflow && this.grade.gradingStatus) { this.workflowStatusTranslationId = AddonModAssign.getSubmissionGradingStatusTranslationId(this.grade.gradingStatus); } - if (this.lastAttempt?.gradingstatus == 'graded' && !this.assign!.markingworkflow && this.userSubmission && feedback) { + if (this.lastAttempt?.gradingstatus == 'graded' && !assign.markingworkflow && this.userSubmission && feedback) { if (feedback.gradeddate < this.userSubmission.timemodified) { this.lastAttempt.gradingstatus = AddonModAssignGradingStates.GRADED_FOLLOWUP_SUBMIT; @@ -685,7 +698,7 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy, Can if (!this.feedback || !this.feedback.plugins) { // Feedback plugins not present, we have to use assign configs to detect the plugins used. this.feedback = AddonModAssignHelper.createEmptyFeedback(); - this.feedback.plugins = AddonModAssignHelper.getPluginsEnabled(this.assign!, 'assignfeedback'); + this.feedback.plugins = AddonModAssignHelper.getPluginsEnabled(assign, 'assignfeedback'); } // Check if there's any offline data for this submission. @@ -700,7 +713,7 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy, Can // Submission grades aren't identified by attempt number so it can retrieve the feedback for a previous attempt. // The app will not treat that as an special case. const submissionGrade = await CoreUtils.ignoreErrors( - AddonModAssignOffline.getSubmissionGrade(this.assign!.id, this.submitId), + AddonModAssignOffline.getSubmissionGrade(assign.id, this.submitId), ); this.hasOfflineGrade = false; @@ -725,10 +738,10 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy, Can if (submissionGrade.outcomes && Object.keys(submissionGrade.outcomes).length && this.gradeInfo?.outcomes) { this.gradeInfo.outcomes.forEach((outcome) => { - if (submissionGrade.outcomes[outcome.itemNumber!] !== undefined) { + if (outcome.itemNumber !== undefined && submissionGrade.outcomes[outcome.itemNumber] !== undefined) { // If outcome has been modified from gradebook, do not use offline. - if (outcome.modified! < submissionGrade.timemodified) { - outcome.selectedId = submissionGrade.outcomes[outcome.itemNumber!]; + if ((outcome.modified || 0) < submissionGrade.timemodified) { + outcome.selectedId = submissionGrade.outcomes[outcome.itemNumber]; this.originalGrades.outcomes[outcome.id] = outcome.selectedId; } } @@ -752,11 +765,15 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy, Can * @param status Submission status. */ protected setStatusNameAndClass(status: AddonModAssignGetSubmissionStatusWSResponse): void { + if (!this.assign) { + return; + } + if (this.hasOffline || this.submittedOffline) { // Offline data. this.statusTranslated = Translate.instant('core.notsent'); this.statusColor = 'warning'; - } else if (!this.assign!.teamsubmission) { + } else if (!this.assign.teamsubmission) { // Single submission. if (this.userSubmission && this.userSubmission.status != this.statusNew) { @@ -775,7 +792,7 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy, Can } else { // Team submission. - if (!status.lastattempt?.submissiongroup && this.assign!.preventsubmissionnotingroup) { + if (!status.lastattempt?.submissiongroup && this.assign.preventsubmissionnotingroup) { this.statusTranslated = Translate.instant('addon.mod_assign.nosubmission'); this.statusColor = AddonModAssign.getSubmissionStatusColor(AddonModAssignSubmissionStatusValues.NO_SUBMISSION); } else if (this.userSubmission && this.userSubmission.status != this.statusNew) { @@ -816,7 +833,11 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy, Can * @param acceptStatement Whether the statement has been accepted. */ async submitForGrading(acceptStatement: boolean): Promise { - if (this.assign!.requiresubmissionstatement && !acceptStatement) { + if (!this.assign || !this.userSubmission) { + return; + } + + if (this.assign.requiresubmissionstatement && !acceptStatement) { CoreDomUtils.showErrorModal('addon.mod_assign.acceptsubmissionstatement', true); return; @@ -830,17 +851,17 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy, Can try { await AddonModAssign.submitForGrading( - this.assign!.id, + this.assign.id, this.courseId, acceptStatement, - this.userSubmission!.timemodified, + this.userSubmission.timemodified, this.hasOffline, ); // Submitted, trigger event. CoreEvents.trigger(AddonModAssignProvider.SUBMITTED_FOR_GRADING_EVENT, { - assignmentId: this.assign!.id, - submissionId: this.userSubmission!.id, + assignmentId: this.assign.id, + submissionId: this.userSubmission.id, userId: this.currentUserId, }, this.siteId); } catch (error) { @@ -861,7 +882,7 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy, Can async submitGrade(): Promise { // Check if there's something to be saved. const modified = await this.hasDataToSave(true); - if (!modified) { + if (!modified || !this.assign) { return; } @@ -880,22 +901,21 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy, Can const modal = await CoreDomUtils.showModalLoading('core.sending', true); (this.gradeInfo?.outcomes || []).forEach((outcome) => { - if (outcome.itemNumber) { - outcomes[outcome.itemNumber] = outcome.selectedId!; + if (outcome.itemNumber && outcome.selectedId) { + outcomes[outcome.itemNumber] = outcome.selectedId; } }); let pluginData: AddonModAssignSavePluginData = {}; try { if (this.feedback && this.feedback.plugins) { - pluginData = - await AddonModAssignHelper.prepareFeedbackPluginData(this.assign!.id, this.submitId, this.feedback); + pluginData = await AddonModAssignHelper.prepareFeedbackPluginData(this.assign.id, this.submitId, this.feedback); } try { // We have all the data, now send it. await AddonModAssign.submitGradingForm( - this.assign!.id, + this.assign.id, this.submitId, this.courseId, grade || 0, @@ -914,7 +934,7 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy, Can this.invalidateAndRefresh(true); CoreEvents.trigger(AddonModAssignProvider.GRADED_EVENT, { - assignmentId: this.assign!.id, + assignmentId: this.assign.id, submissionId: this.submitId, userId: this.currentUserId, }, this.siteId); @@ -939,21 +959,21 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy, Can this.isGrading = true; // Make sure outcomes is an array. - this.gradeInfo.outcomes = this.gradeInfo.outcomes || []; + const gradeInfo = this.gradeInfo; + gradeInfo.outcomes = gradeInfo.outcomes || []; // Check if grading method is simple or not. - if (this.gradeInfo.advancedgrading && this.gradeInfo.advancedgrading[0] && - this.gradeInfo.advancedgrading[0].method !== undefined) { - this.grade.method = this.gradeInfo.advancedgrading[0].method || 'simple'; + if (gradeInfo.advancedgrading && gradeInfo.advancedgrading[0] && gradeInfo.advancedgrading[0].method !== undefined) { + this.grade.method = gradeInfo.advancedgrading[0].method || 'simple'; } else { this.grade.method = 'simple'; } this.canSaveGrades = this.grade.method == 'simple'; // Grades can be saved if simple grading. - if (this.gradeInfo.scale) { + if (gradeInfo.scale) { this.grade.scale = - CoreUtils.makeMenuFromList(this.gradeInfo.scale, Translate.instant('core.nograde')); + CoreUtils.makeMenuFromList(gradeInfo.scale, Translate.instant('core.nograde')); } else { // Format the grade. this.grade.grade = CoreUtils.formatFloat(this.grade.grade); @@ -964,8 +984,8 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy, Can } // Treat outcomes. - if (this.gradeInfo.outcomes) { - this.gradeInfo.outcomes.forEach((outcome) => { + if (gradeInfo.outcomes) { + gradeInfo.outcomes.forEach((outcome) => { if (outcome.scale) { outcome.options = CoreUtils.makeMenuFromList( @@ -986,7 +1006,8 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy, Can grades.forEach((grade: CoreGradesFormattedItem) => { if (!grade.outcomeid && !grade.scaleid) { - const gradeFormatted = grade.gradeformatted || ''; + // Clean HTML tags, grade can contain an icon. + const gradeFormatted = CoreTextUtils.cleanTags(grade.gradeformatted || ''); // Not using outcomes or scale, get the numeric grade. if (this.grade.scale) { this.grade.gradebookGrade = CoreUtils.formatFloat( @@ -1004,7 +1025,7 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy, Can } else if (grade.outcomeid) { // Only show outcomes with info on it, outcomeid could be null if outcomes are disabled on site. - this.gradeInfo!.outcomes && this.gradeInfo!.outcomes.forEach((outcome) => { + gradeInfo.outcomes?.forEach((outcome) => { if (outcome.id == String(grade.outcomeid)) { outcome.selected = grade.gradeformatted; outcome.modified = grade.gradedategraded; @@ -1017,38 +1038,42 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy, Can outcomes.push(outcome); } }); - this.gradeInfo!.disabled = grade.gradeislocked || grade.gradeisoverridden; + gradeInfo.disabled = grade.gradeislocked || grade.gradeisoverridden; } }); - this.gradeInfo.outcomes = outcomes; + gradeInfo.outcomes = outcomes; } /** * Treat the last attempt. * * @param submissionStatus Response of get submission status. + * @param lastAttempt Last attempt (if any). * @param promises List where to add the promises. */ - protected treatLastAttempt(submissionStatus: AddonModAssignGetSubmissionStatusWSResponse): Promise[] { + protected treatLastAttempt( + submissionStatus: AddonModAssignGetSubmissionStatusWSResponse, + lastAttempt?: AddonModAssignSubmissionAttemptFormatted, + ): Promise[] { const promises: Promise[] =[]; - if (!submissionStatus.lastattempt) { + if (!lastAttempt || !this.assign) { return []; } - const submissionStatementMissing = !!this.assign!.requiresubmissionstatement && - this.assign!.submissionstatement === undefined; + const submissionStatementMissing = !!this.assign.requiresubmissionstatement && + this.assign.submissionstatement === undefined; - this.canSubmit = !this.isSubmittedForGrading && !this.submittedOffline && (submissionStatus.lastattempt.cansubmit || - (this.hasOffline && AddonModAssign.canSubmitOffline(this.assign!, submissionStatus))); + this.canSubmit = !this.isSubmittedForGrading && !this.submittedOffline && (lastAttempt.cansubmit || + (this.hasOffline && AddonModAssign.canSubmitOffline(this.assign, submissionStatus))); - this.canEdit = !this.isSubmittedForGrading && submissionStatus.lastattempt.canedit && - (!this.submittedOffline || !this.assign!.submissiondrafts); + this.canEdit = !this.isSubmittedForGrading && lastAttempt.canedit && + (!this.submittedOffline || !this.assign.submissiondrafts); // Get submission statement if needed. - if (this.assign!.requiresubmissionstatement && this.assign!.submissiondrafts && this.submitId == this.currentUserId) { - this.submissionStatement = this.assign!.submissionstatement; + if (this.assign.requiresubmissionstatement && this.assign.submissiondrafts && this.submitId == this.currentUserId) { + this.submissionStatement = this.assign.submissionstatement; this.acceptStatement = false; } else { this.submissionStatement = undefined; @@ -1056,26 +1081,26 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy, Can } // Show error if submission statement should be shown but it couldn't be retrieved. - this.showErrorStatementEdit = submissionStatementMissing && !this.assign!.submissiondrafts && + this.showErrorStatementEdit = submissionStatementMissing && !this.assign.submissiondrafts && this.submitId == this.currentUserId; - this.showErrorStatementSubmit = submissionStatementMissing && !!this.assign!.submissiondrafts; + this.showErrorStatementSubmit = submissionStatementMissing && !!this.assign.submissiondrafts; - this.userSubmission = AddonModAssign.getSubmissionObjectFromAttempt(this.assign!, submissionStatus.lastattempt); + this.userSubmission = AddonModAssign.getSubmissionObjectFromAttempt(this.assign, lastAttempt); - if (this.assign!.attemptreopenmethod != this.attemptReopenMethodNone && this.userSubmission) { + if (this.assign.attemptreopenmethod != this.attemptReopenMethodNone && this.userSubmission) { this.currentAttempt = this.userSubmission.attemptnumber + 1; } this.setStatusNameAndClass(submissionStatus); - if (this.assign!.teamsubmission) { - if (submissionStatus.lastattempt.submissiongroup) { + if (this.assign.teamsubmission) { + if (lastAttempt.submissiongroup) { // Get the name of the group. - promises.push(CoreGroups.getActivityAllowedGroups(this.assign!.cmid).then((result) => { - const group = result.groups.find((group) => group.id == submissionStatus.lastattempt!.submissiongroup); + promises.push(CoreGroups.getActivityAllowedGroups(this.assign.cmid).then((result) => { + const group = result.groups.find((group) => group.id === lastAttempt.submissiongroup); if (group) { - this.lastAttempt!.submissiongroupname = group.name; + lastAttempt.submissiongroupname = group.name; } return; @@ -1085,9 +1110,9 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy, Can // Get the members that need to submit. if (this.userSubmission && this.userSubmission.status != this.statusNew && - submissionStatus.lastattempt.submissiongroupmemberswhoneedtosubmit + lastAttempt.submissiongroupmemberswhoneedtosubmit ) { - submissionStatus.lastattempt.submissiongroupmemberswhoneedtosubmit.forEach((member) => { + lastAttempt.submissiongroupmemberswhoneedtosubmit.forEach((member) => { if (!this.blindMarking) { promises.push(CoreUser.getProfile(member, this.courseId).then((profile) => { this.membersToSubmit.push(profile); @@ -1100,23 +1125,20 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy, Can } // Get grading text and color. - this.gradingStatusTranslationId = AddonModAssign.getSubmissionGradingStatusTranslationId( - submissionStatus.lastattempt.gradingstatus, - ); - this.gradingColor = AddonModAssign.getSubmissionGradingStatusColor(submissionStatus.lastattempt.gradingstatus); + this.gradingStatusTranslationId = AddonModAssign.getSubmissionGradingStatusTranslationId(lastAttempt.gradingstatus); + this.gradingColor = AddonModAssign.getSubmissionGradingStatusColor(lastAttempt.gradingstatus); // Get the submission plugins. if (this.userSubmission) { - if (!this.assign!.teamsubmission || - !submissionStatus.lastattempt.submissiongroup || - !this.assign!.preventsubmissionnotingroup + if (!this.assign.teamsubmission || + !lastAttempt.submissiongroup || + !this.assign.preventsubmissionnotingroup ) { - if (this.previousAttempt && this.previousAttempt.submission!.plugins && - this.userSubmission.status == this.statusReopened) { + if (this.previousAttempt?.submission?.plugins && this.userSubmission.status === this.statusReopened) { // Get latest attempt if available. - this.submissionPlugins = this.previousAttempt.submission!.plugins; + this.submissionPlugins = this.previousAttempt.submission.plugins; } else { - this.submissionPlugins = this.userSubmission.plugins!; + this.submissionPlugins = this.userSubmission.plugins || []; } } } @@ -1134,7 +1156,7 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy, Can return; } - const syncId = AddonModAssignSync.getGradeSyncId(this.assign!.id, this.submitId); + const syncId = AddonModAssignSync.getGradeSyncId(this.assign.id, this.submitId); if (block) { CoreSync.blockOperation(AddonModAssignProvider.COMPONENT, syncId); diff --git a/src/core/features/grades/lang.json b/src/core/features/grades/lang.json index 1084e7782..504a4ae34 100644 --- a/src/core/features/grades/lang.json +++ b/src/core/features/grades/lang.json @@ -6,6 +6,7 @@ "calculatedgrade": "Calculated grade", "category": "Category", "contributiontocoursetotal": "Contribution to course total", + "fail": "Fail", "feedback": "Feedback", "grade": "Grade", "gradeitem": "Grade item", @@ -16,6 +17,7 @@ "nogradesreturned": "No grades returned", "nooutcome": "No outcome", "outcome": "Outcome", + "pass": "Pass", "percentage": "Percentage", "range": "Range", "rank": "Rank", diff --git a/src/core/features/grades/pages/course/course.html b/src/core/features/grades/pages/course/course.html index 497f485a6..053b16cd1 100644 --- a/src/core/features/grades/pages/course/course.html +++ b/src/core/features/grades/pages/course/course.html @@ -49,8 +49,8 @@ - + + + + + diff --git a/src/core/features/grades/pages/course/course.scss b/src/core/features/grades/pages/course/course.scss index 9068aaba0..dec25329c 100644 --- a/src/core/features/grades/pages/course/course.scss +++ b/src/core/features/grades/pages/course/course.scss @@ -42,7 +42,7 @@ } th, td { - @include padding(10px, 10px, 10px, null); + @include padding(8px, 8px, 8px, null); vertical-align: top; white-space: normal; text-align: start; @@ -55,7 +55,7 @@ } thead #gradeitem { - @include padding(null, null, null, 23px); + @include padding(null, null, null, 24px); } tbody th { @@ -63,11 +63,11 @@ } tbody #gradeitem { - @include padding(null, null, null, 5px); + @include padding(null, null, null, 4px); } .core-grades-table-gradeitem { - @include padding(null, null, null, 5px); + @include padding(null, null, null, 4px); font-weight: bold; &.column-itemname { @@ -90,7 +90,7 @@ } span { - @include margin(null, null, null, 5px); + @include margin(null, null, null, 4px); } .expandable-status-icon { @@ -101,7 +101,7 @@ } .core-grades-table-feedback { - @include padding(null, null, null, 5px); + @include padding(null, null, null, 4px); .no-overflow { overflow: auto; @@ -109,6 +109,12 @@ } + .core-grades-table-grade { + ion-icon { + @include padding(null, 4px, null, null); + } + } + .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 cf83ea121..c343c2c71 100644 --- a/src/core/features/grades/services/grades-helper.ts +++ b/src/core/features/grades/services/grades-helper.ts @@ -108,7 +108,7 @@ export class CoreGradesHelperProvider { let content = String(column.content); - if (name == 'itemname') { + if (name === 'itemname') { const itemNameColumn = column; row.id = parseInt(itemNameColumn.id.split('_')[1], 10); @@ -123,6 +123,20 @@ export class CoreGradesHelperProvider { content = content.replace(/<\/span>/gi, '\n'); content = CoreTextUtils.cleanTags(content); name = 'gradeitem'; + } else if (name === 'grade') { + // Add the pass/fail class if present. + row.gradeClass = column.class.includes('gradepass') ? 'text-success' : + (column.class.includes('gradefail') ? 'text-danger' : ''); + + if (content.includes('fa-check')) { + row.gradeIcon = 'fas-check'; + row.gradeIconAlt = Translate.instant('core.grades.pass'); + content = CoreTextUtils.cleanTags(content); + } else if (content.includes('fa-times')) { + row.gradeIcon = 'fas-times'; + row.gradeIconAlt = Translate.instant('core.grades.fail'); + content = CoreTextUtils.cleanTags(content); + } } else { content = CoreTextUtils.replaceNewLines(content, '
'); } @@ -721,6 +735,9 @@ export type CoreGradesFormattedTableRow = CoreGradesFormattedRowCommonData & { ariaLabel?: string; expandable?: boolean; expanded?: boolean; + gradeClass?: string; + gradeIcon?: string; + gradeIconAlt?: string; }; export type CoreGradesFormattedTableColumn = { diff --git a/src/core/features/h5p/classes/content-validator.ts b/src/core/features/h5p/classes/content-validator.ts index f7fc315ba..20c2cb69b 100644 --- a/src/core/features/h5p/classes/content-validator.ts +++ b/src/core/features/h5p/classes/content-validator.ts @@ -357,7 +357,7 @@ export class CoreH5PContentValidator { } // Remove temporary files suffix. - if (file.path.substring(-4, 4) === '#tmp') { + if (file.path.slice(-4) === '#tmp') { file.path = file.path.substring(0, file.path.length - 4); }