MOBILE-3413 h5pactivity: Implement viewing attempt results
This commit is contained in:
		
							parent
							
								
									22caea43ba
								
							
						
					
					
						commit
						b48a27e656
					
				| @ -659,12 +659,39 @@ | ||||
|   "addon.mod_glossary.noentriesfound": "local_moodlemobileapp", | ||||
|   "addon.mod_glossary.searchquery": "local_moodlemobileapp", | ||||
|   "addon.mod_glossary.tagarea_glossary_entries": "glossary", | ||||
|   "addon.mod_h5pactivity.all_attempts": "h5pactivity", | ||||
|   "addon.mod_h5pactivity.answer_checked": "h5pactivity", | ||||
|   "addon.mod_h5pactivity.answer_correct": "h5pactivity", | ||||
|   "addon.mod_h5pactivity.answer_fail": "h5pactivity", | ||||
|   "addon.mod_h5pactivity.answer_incorrect": "h5pactivity", | ||||
|   "addon.mod_h5pactivity.answer_pass": "h5pactivity", | ||||
|   "addon.mod_h5pactivity.attempt": "h5pactivity", | ||||
|   "addon.mod_h5pactivity.attempt_completion_no": "h5pactivity", | ||||
|   "addon.mod_h5pactivity.attempt_completion_yes": "h5pactivity", | ||||
|   "addon.mod_h5pactivity.attempt_success_fail": "h5pactivity", | ||||
|   "addon.mod_h5pactivity.attempt_success_pass": "h5pactivity", | ||||
|   "addon.mod_h5pactivity.attempt_success_unknown": "h5pactivity", | ||||
|   "addon.mod_h5pactivity.attempts_none": "h5pactivity", | ||||
|   "addon.mod_h5pactivity.completion": "h5pactivity", | ||||
|   "addon.mod_h5pactivity.downloadh5pfile": "local_moodlemobileapp", | ||||
|   "addon.mod_h5pactivity.duration": "h5pactivity", | ||||
|   "addon.mod_h5pactivity.errorgetactivity": "local_moodlemobileapp", | ||||
|   "addon.mod_h5pactivity.filestatenotdownloaded": "local_moodlemobileapp", | ||||
|   "addon.mod_h5pactivity.filestateoutdated": "local_moodlemobileapp", | ||||
|   "addon.mod_h5pactivity.maxscore": "h5pactivity", | ||||
|   "addon.mod_h5pactivity.modulenameplural": "h5pactivity", | ||||
|   "addon.mod_h5pactivity.myattempts": "h5pactivity", | ||||
|   "addon.mod_h5pactivity.no_compatible_track": "h5pactivity", | ||||
|   "addon.mod_h5pactivity.offlinedisabledwarning": "local_moodlemobileapp", | ||||
|   "addon.mod_h5pactivity.outcome": "h5pactivity", | ||||
|   "addon.mod_h5pactivity.result_fill-in": "h5pactivity", | ||||
|   "addon.mod_h5pactivity.result_other": "h5pactivity", | ||||
|   "addon.mod_h5pactivity.review_my_attempts": "h5pactivity", | ||||
|   "addon.mod_h5pactivity.score": "h5pactivity", | ||||
|   "addon.mod_h5pactivity.score_out_of": "h5pactivity", | ||||
|   "addon.mod_h5pactivity.startdate": "h5pactivity", | ||||
|   "addon.mod_h5pactivity.totalscore": "h5pactivity", | ||||
|   "addon.mod_h5pactivity.viewattempt": "local_moodlemobileapp", | ||||
|   "addon.mod_imscp.deploymenterror": "imscp", | ||||
|   "addon.mod_imscp.modulenameplural": "imscp", | ||||
|   "addon.mod_imscp.showmoduledescription": "local_moodlemobileapp", | ||||
|  | ||||
| @ -1,5 +1,11 @@ | ||||
| { | ||||
|     "all_attempts": "All user attempts", | ||||
|     "answer_checked": "Answer checked", | ||||
|     "answer_correct": "Your answer is correct", | ||||
|     "answer_fail": "Incorrect answer", | ||||
|     "answer_incorrect": "Your answer is incorrect", | ||||
|     "answer_pass": "Correct answer", | ||||
|     "attempt": "Attempt", | ||||
|     "attempt_completion_no": "This attempt is not marked as completed", | ||||
|     "attempt_completion_yes": "This attempt is completed", | ||||
|     "attempts_none": "This user has no attempts to display.", | ||||
| @ -15,8 +21,15 @@ | ||||
|     "maxscore": "Max score", | ||||
|     "modulenameplural": "H5P", | ||||
|     "myattempts": "My attempts", | ||||
|     "no_compatible_track": "This interaction ({{$a}}) does not provide tracking information or the tracking provided is not compatible with the current activity version.", | ||||
|     "offlinedisabledwarning": "You will need to be online to view the H5P package.", | ||||
|     "outcome": "Outcome", | ||||
|     "result_fill-in": "Fill-in text", | ||||
|     "result_other": "Unkown interaction type", | ||||
|     "review_my_attempts": "View my attempts", | ||||
|     "score": "Score", | ||||
|     "score_out_of": "{{$a.rawscore}} out of {{$a.maxscore}}", | ||||
|     "startdate": "Start date", | ||||
|     "totalscore": "Total score", | ||||
|     "viewattempt": "View attempt {{$a}}" | ||||
| } | ||||
| @ -0,0 +1,140 @@ | ||||
| <ion-header> | ||||
|     <ion-navbar core-back-button> | ||||
|         <ion-title><core-format-text *ngIf="h5pActivity" [text]="h5pActivity.name" contextLevel="module" [contextInstanceId]="h5pActivity.coursemodule" [courseId]="courseId"></core-format-text></ion-title> | ||||
|     </ion-navbar> | ||||
| </ion-header> | ||||
| <ion-content> | ||||
|     <ion-refresher [enabled]="loaded" (ionRefresh)="doRefresh($event)"> | ||||
|         <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content> | ||||
|     </ion-refresher> | ||||
|     <core-loading [hideUntil]="loaded"> | ||||
|         <ng-container *ngIf="attempt"> | ||||
|             <!-- Attempt number and user that did the attempt. --> | ||||
|             <a ion-item text-wrap *ngIf="user" core-user-link [userId]="user.id" [courseId]="courseId" [title]="user.fullname"> | ||||
|                 <ion-avatar core-user-avatar [user]="user" item-start></ion-avatar> | ||||
|                 <h2>{{ 'addon.mod_h5pactivity.attempt' | translate }} #{{attempt.attempt}}: {{user.fullname}}</h2> | ||||
|             </a> | ||||
|             <!-- Attempt number (if user not known). --> | ||||
|             <ion-item text-wrap *ngIf="!user"> | ||||
|                 <h2>{{ 'addon.mod_h5pactivity.attempt' | translate }} #{{attempt.attempt}}</h2> | ||||
|             </ion-item> | ||||
| 
 | ||||
|             <!-- Attempt summary. --> | ||||
|             <ion-card class="addon-mod_h5pactivity-attempt-result-summary"> | ||||
|                 <ion-list> | ||||
|                     <ion-item text-wrap no-lines> | ||||
|                         <h2>{{ 'addon.mod_h5pactivity.startdate' | translate }}</h2> | ||||
|                         <p>{{ attempt.timecreated | coreFormatDate:'strftimedatetime' }}</p> | ||||
|                     </ion-item> | ||||
|                     <ion-item text-wrap no-lines> | ||||
|                         <h2>{{ 'addon.mod_h5pactivity.completion' | translate }}</h2> | ||||
|                         <p *ngIf="attempt.completion"> | ||||
|                             <img src="assets/img/completion/completion-auto-y.svg" role="presentation" alt=""> | ||||
|                             {{ 'addon.mod_h5pactivity.attempt_completion_yes' | translate }} | ||||
|                         </p> | ||||
|                         <p *ngIf="!attempt.completion"> | ||||
|                             <img src="assets/img/completion/completion-auto-n.svg" role="presentation" alt=""> | ||||
|                             {{ 'addon.mod_h5pactivity.attempt_completion_no' | translate }} | ||||
|                         </p> | ||||
|                     </ion-item> | ||||
|                     <ion-item text-wrap no-lines> | ||||
|                         <h2>{{ 'addon.mod_h5pactivity.duration' | translate }}</h2> | ||||
|                         <p>{{ attempt.durationReadable }}</p> | ||||
|                     </ion-item> | ||||
|                     <ion-item text-wrap no-lines> | ||||
|                         <h2>{{ 'addon.mod_h5pactivity.outcome' | translate }}</h2> | ||||
|                         <p *ngIf="attempt.success !== null && attempt.success" > | ||||
|                             <core-icon name="fa-check-circle"></core-icon> | ||||
|                             {{ 'addon.mod_h5pactivity.attempt_success_pass' | translate }} | ||||
|                         </p> | ||||
|                         <p *ngIf="attempt.success !== null && !attempt.success" > | ||||
|                             <core-icon name="fa-circle-o"></core-icon> | ||||
|                             {{ 'addon.mod_h5pactivity.attempt_success_fail' | translate }} | ||||
|                         </p> | ||||
|                         <p *ngIf="attempt.success === null" > | ||||
|                             {{ 'addon.mod_h5pactivity.attempt_success_unknown' | translate }} | ||||
|                         </p> | ||||
|                     </ion-item> | ||||
|                     <ion-item *ngIf="attempt.maxscore" text-wrap no-lines> | ||||
|                         <h2>{{ 'addon.mod_h5pactivity.totalscore' | translate }}</h2> | ||||
|                         <p>{{ 'addon.mod_h5pactivity.score_out_of' | translate:{$a: attempt} }}</p> | ||||
|                     </ion-item> | ||||
|                 </ion-list> | ||||
|             </ion-card> | ||||
| 
 | ||||
|             <!-- Results. --> | ||||
|             <ng-container *ngIf="attempt.results"> | ||||
|                 <ion-card *ngFor="let result of attempt.results"> | ||||
|                     <ion-card-header text-wrap> | ||||
|                         <core-format-text [text]="result.description" [component]="component" [componentId]="h5pActivity.cmid" contextLevel="module" [contextInstanceId]="h5pActivity.cmid" [courseId]="courseId"></core-format-text> | ||||
|                     </ion-card-header> | ||||
|                     <ion-item *ngIf="result.content" text-wrap> | ||||
|                         <core-format-text [text]="result.content" [component]="component" [componentId]="h5pActivity.cmid" contextLevel="module" [contextInstanceId]="h5pActivity.cmid" [courseId]="courseId"></core-format-text> | ||||
|                     </ion-item> | ||||
| 
 | ||||
|                     <!-- Options. --> | ||||
|                     <ng-container *ngIf="result.options && result.options.length"> | ||||
|                         <ion-item text-wrap class="addon-mod_h5pactivity-result-table-header"> | ||||
|                             <ion-row align-items-center> | ||||
|                                 <ion-col text-center>{{ result.optionslabel }}</ion-col> | ||||
|                                 <ion-col text-center>{{ result.correctlabel }}</ion-col> | ||||
|                                 <ion-col text-center>{{ result.answerlabel }}</ion-col> | ||||
|                             </ion-row> | ||||
|                         </ion-item> | ||||
|                         <ion-item text-wrap *ngFor="let option of result.options" class="addon-mod_h5pactivity-result-table-row"> | ||||
|                             <ion-row align-items-center> | ||||
|                                 <ion-col text-center> | ||||
|                                     <core-format-text [text]="option.description" [component]="component" [componentId]="h5pActivity.cmid" contextLevel="module" [contextInstanceId]="h5pActivity.cmid" [courseId]="courseId"></core-format-text> | ||||
|                                 </ion-col> | ||||
|                                 <ion-col text-center> | ||||
|                                     <ng-container *ngIf="option.correctanswer"> | ||||
|                                         <ng-container *ngTemplateOutlet="answerTemplate; context: {answer: option.correctanswer}"></ng-container> | ||||
|                                     </ng-container> | ||||
|                                 </ion-col> | ||||
|                                 <ion-col text-center> | ||||
|                                     <ng-container *ngIf="option.useranswer"> | ||||
|                                         <ng-container *ngTemplateOutlet="answerTemplate; context: {answer: option.useranswer}"></ng-container> | ||||
|                                     </ng-container> | ||||
|                                 </ion-col> | ||||
|                             </ion-row> | ||||
|                         </ion-item> | ||||
| 
 | ||||
|                         <!-- Result score. --> | ||||
|                         <ion-item *ngIf="result.maxscore" text-wrap text-end class="addon-mod_h5pactivity-result-score"> | ||||
|                             <p><strong>{{ 'addon.mod_h5pactivity.score' | translate }}: {{ 'addon.mod_h5pactivity.score_out_of' | translate:{$a: result} }}</strong></p> | ||||
|                         </ion-item> | ||||
|                     </ng-container> | ||||
| 
 | ||||
|                     <!-- Result doesn't support tracking. --> | ||||
|                     <ion-item text-wrap class="core-warning-item" *ngIf="!result.track"> | ||||
|                         <ion-icon item-start name="warning" color="warning"></ion-icon> {{ 'addon.mod_h5pactivity.no_compatible_track' | translate:{$a: result.interactiontype} }} | ||||
|                     </ion-item> | ||||
|                 </ion-card> | ||||
|             </ng-container> | ||||
|         </ng-container> | ||||
|     </core-loading> | ||||
| </ion-content> | ||||
| 
 | ||||
| <!-- Template to render an answer. --> | ||||
| <ng-template #answerTemplate let-answer="answer"> | ||||
|     <p *ngIf="answer.correct"> | ||||
|         <core-icon name="fa-check" [label]="'addon.mod_h5pactivity.answer_correct' | translate" color="success"></core-icon> | ||||
|         {{ answer.answer }} | ||||
|     </p> | ||||
|     <p *ngIf="answer.incorrect"> | ||||
|         <core-icon name="fa-remove" [label]="'addon.mod_h5pactivity.answer_incorrect' | translate" color="danger"></core-icon> | ||||
|         {{ answer.answer }} | ||||
|     </p> | ||||
|     <p *ngIf="answer.text"> | ||||
|         {{ answer.answer }} | ||||
|     </p> | ||||
|     <p *ngIf="answer.checked"> | ||||
|         <core-icon name="fa-check-circle" [label]="'addon.mod_h5pactivity.answer_checked' | translate"></core-icon> | ||||
|     </p> | ||||
|     <p *ngIf="answer.pass"> | ||||
|         <core-icon name="fa-check" [label]="'addon.mod_h5pactivity.answer_pass' | translate" color="success"></core-icon> | ||||
|     </p> | ||||
|     <p *ngIf="answer.fail"> | ||||
|         <core-icon name="fa-remove" [label]="'addon.mod_h5pactivity.answer_fail' | translate" color="danger"></core-icon> | ||||
|     </p> | ||||
| </ng-template> | ||||
| @ -0,0 +1,35 @@ | ||||
| // (C) Copyright 2015 Moodle Pty Ltd.
 | ||||
| //
 | ||||
| // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||
| // you may not use this file except in compliance with the License.
 | ||||
| // You may obtain a copy of the License at
 | ||||
| //
 | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
| //
 | ||||
| // Unless required by applicable law or agreed to in writing, software
 | ||||
| // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| import { NgModule } from '@angular/core'; | ||||
| import { IonicPageModule } from 'ionic-angular'; | ||||
| import { TranslateModule } from '@ngx-translate/core'; | ||||
| import { CoreComponentsModule } from '@components/components.module'; | ||||
| import { CoreDirectivesModule } from '@directives/directives.module'; | ||||
| import { CorePipesModule } from '@pipes/pipes.module'; | ||||
| import { AddonModH5PActivityAttemptResultsPage } from './attempt-results'; | ||||
| 
 | ||||
| @NgModule({ | ||||
|     declarations: [ | ||||
|         AddonModH5PActivityAttemptResultsPage, | ||||
|     ], | ||||
|     imports: [ | ||||
|         CoreComponentsModule, | ||||
|         CoreDirectivesModule, | ||||
|         CorePipesModule, | ||||
|         IonicPageModule.forChild(AddonModH5PActivityAttemptResultsPage), | ||||
|         TranslateModule.forChild(), | ||||
|     ], | ||||
| }) | ||||
| export class AddonModH5PActivityAttemptResultsPageModule {} | ||||
| @ -0,0 +1,55 @@ | ||||
| ion-app.app-root page-addon-mod-h5pactivity-attempt-results { | ||||
| 
 | ||||
|     .addon-mod_h5pactivity-attempt-result-summary { | ||||
|         img { | ||||
|             width: 16px; | ||||
|             height: 16px; | ||||
|             display: inline; | ||||
|             @include margin-horizontal(0, 4px); | ||||
|         } | ||||
| 
 | ||||
|         .icon { | ||||
|             font-size: 1.4em; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     .addon-mod_h5pactivity-result-table-header .item-inner { | ||||
|         font-size: 0.9em; | ||||
|         font-weight: bold; | ||||
| 
 | ||||
|         .col[text-center] { | ||||
|             @include padding-horizontal(0); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     .addon-mod_h5pactivity-result-table-header, .addon-mod_h5pactivity-result-table-row { | ||||
| 
 | ||||
|         .item-inner ion-label { | ||||
|             @include margin(null, 0, null, null); | ||||
|         } | ||||
| 
 | ||||
|         .item { | ||||
|             @include padding(null, null, null, 0); | ||||
|         } | ||||
| 
 | ||||
|         .label { | ||||
|             margin-top: 0; | ||||
|             margin-bottom: 0; | ||||
|         } | ||||
| 
 | ||||
|         .icon { | ||||
|             font-size: 1.2em; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     .addon-mod_h5pactivity-result-table-row.item:nth-child(even) { | ||||
|         background-color: $gray-lighter; | ||||
|         @include darkmode() { | ||||
|             background-color: $core-dark-item-divider-bg-color; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     .addon-mod_h5pactivity-result-score { | ||||
|         border-top: 1px solid black; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,133 @@ | ||||
| // (C) Copyright 2015 Moodle Pty Ltd.
 | ||||
| //
 | ||||
| // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||
| // you may not use this file except in compliance with the License.
 | ||||
| // You may obtain a copy of the License at
 | ||||
| //
 | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
| //
 | ||||
| // Unless required by applicable law or agreed to in writing, software
 | ||||
| // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| import { Component, OnInit } from '@angular/core'; | ||||
| import { IonicPage, NavParams } from 'ionic-angular'; | ||||
| import { CoreDomUtils } from '@providers/utils/dom'; | ||||
| import { CoreUser } from '@core/user/providers/user'; | ||||
| import { | ||||
|     AddonModH5PActivity, AddonModH5PActivityProvider, AddonModH5PActivityData, AddonModH5PActivityAttemptResults | ||||
| } from '../../providers/h5pactivity'; | ||||
| 
 | ||||
| /** | ||||
|  * Page that displays results of an attempt. | ||||
|  */ | ||||
| @IonicPage({ segment: 'addon-mod-h5pactivity-attempt-results' }) | ||||
| @Component({ | ||||
|     selector: 'page-addon-mod-h5pactivity-attempt-results', | ||||
|     templateUrl: 'attempt-results.html', | ||||
| }) | ||||
| export class AddonModH5PActivityAttemptResultsPage implements OnInit { | ||||
|     loaded: boolean; | ||||
|     h5pActivity: AddonModH5PActivityData; | ||||
|     attempt: AddonModH5PActivityAttemptResults; | ||||
|     user: any; | ||||
|     component = AddonModH5PActivityProvider.COMPONENT; | ||||
| 
 | ||||
|     protected courseId: number; | ||||
|     protected h5pActivityId: number; | ||||
|     protected attemptId: number; | ||||
| 
 | ||||
|     constructor(navParams: NavParams) { | ||||
|         this.courseId = navParams.get('courseId'); | ||||
|         this.h5pActivityId = navParams.get('h5pActivityId'); | ||||
|         this.attemptId = navParams.get('attemptId'); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Component being initialized. | ||||
|      * | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     async ngOnInit(): Promise<void> { | ||||
|         try { | ||||
|             await this.fetchData(); | ||||
|         } catch (error) { | ||||
|             CoreDomUtils.instance.showErrorModalDefault(error, 'Error loading attempt.'); | ||||
|         } finally { | ||||
|             this.loaded = true; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Refresh the data. | ||||
|      * | ||||
|      * @param refresher Refresher. | ||||
|      */ | ||||
|     doRefresh(refresher: any): void { | ||||
|         this.refreshData().finally(() => { | ||||
|             refresher.complete(); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get quiz data and attempt data. | ||||
|      * | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     protected async fetchData(): Promise<void> { | ||||
|         await Promise.all([ | ||||
|             this.fetchActivity(), | ||||
|             this.fetchAttempt(), | ||||
|         ]); | ||||
| 
 | ||||
|         await this.fetchUserProfile(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get activity data. | ||||
|      * | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     protected async fetchActivity(): Promise<void> { | ||||
|         this.h5pActivity = await AddonModH5PActivity.instance.getH5PActivityById(this.courseId, this.h5pActivityId); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get attempts. | ||||
|      * | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     protected async fetchAttempt(): Promise<void> { | ||||
|         this.attempt = await AddonModH5PActivity.instance.getResults(this.h5pActivityId, this.attemptId); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get user profile. | ||||
|      * | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     protected async fetchUserProfile(): Promise<void> { | ||||
|         this.user = await CoreUser.instance.getProfile(this.attempt.userid, this.courseId, true); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Refresh the data. | ||||
|      * | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     protected async refreshData(): Promise<void> { | ||||
| 
 | ||||
|         try { | ||||
|             await Promise.all([ | ||||
|                 AddonModH5PActivity.instance.invalidateActivityData(this.courseId), | ||||
|                 AddonModH5PActivity.instance.invalidateAttemptResults(this.h5pActivityId, this.attemptId), | ||||
|             ]); | ||||
|         } catch (error) { | ||||
|             // Ignore errors.
 | ||||
|         } | ||||
| 
 | ||||
|         await this.fetchData(); | ||||
|     } | ||||
| } | ||||
| @ -9,13 +9,11 @@ | ||||
|     </ion-refresher> | ||||
|     <core-loading [hideUntil]="loaded"> | ||||
|         <!-- User viewed. --> | ||||
|         <ion-list *ngIf="user"> | ||||
|              <ion-item text-wrap> | ||||
|         <a ion-item text-wrap *ngIf="user" core-user-link [userId]="user.id" [courseId]="courseId" [title]="user.fullname"> | ||||
|             <ion-avatar core-user-avatar [user]="user" item-start></ion-avatar> | ||||
|             <h2 *ngIf="!isCurrentUser">{{ user.fullname }}</h2> | ||||
|             <h2 *ngIf="isCurrentUser">{{ 'addon.mod_h5pactivity.myattempts' | translate }}</h2> | ||||
|             </ion-item> | ||||
|         </ion-list> | ||||
|         </a> | ||||
| 
 | ||||
|         <ion-list *ngIf="attemptsData"> | ||||
|             <!-- Attempts used to calculate the score. --> | ||||
| @ -57,7 +55,7 @@ | ||||
|     </ion-item> | ||||
| 
 | ||||
|     <!-- List of attempts. --> | ||||
|     <a ion-item text-wrap *ngFor="let attempt of attempts" [attr.aria-label]="'addon.mod_h5pactivity.viewattempt' | translate:{$a: attempt.attempt}" class="addon-mod_h5pactivity-table-row"> | ||||
|     <a ion-item text-wrap *ngFor="let attempt of attempts" [attr.aria-label]="'addon.mod_h5pactivity.viewattempt' | translate:{$a: attempt.attempt}" class="addon-mod_h5pactivity-table-row" navPush="AddonModH5PActivityAttemptResultsPage" [navParams]="{courseId: courseId, h5pActivityId: h5pActivityId, attemptId: attempt.id}"> | ||||
|         <ion-row align-items-center> | ||||
|             <ion-col text-center>{{ attempt.attempt }}</ion-col> | ||||
|             <ion-col text-center col-5 col-md-2>{{ attempt.timemodified | coreFormatDate:'strftimedatetimeshort' }}</ion-col> | ||||
|  | ||||
| @ -31,13 +31,13 @@ import { | ||||
| }) | ||||
| export class AddonModH5PActivityUserAttemptsPage implements OnInit { | ||||
|     loaded: boolean; | ||||
|     courseId: number; | ||||
|     h5pActivityId: number; | ||||
|     h5pActivity: AddonModH5PActivityData; | ||||
|     attemptsData: AddonModH5PActivityUserAttempts; | ||||
|     user: any; | ||||
|     isCurrentUser: boolean; | ||||
| 
 | ||||
|     protected courseId: number; | ||||
|     protected h5pActivityId: number; | ||||
|     protected userId: number; | ||||
| 
 | ||||
|     constructor(navParams: NavParams) { | ||||
|  | ||||
| @ -41,9 +41,32 @@ export class AddonModH5PActivityProvider { | ||||
|     protected formatAttempt(attempt: AddonModH5PActivityWSAttempt): AddonModH5PActivityAttempt { | ||||
|         const formattedAttempt: AddonModH5PActivityAttempt = attempt; | ||||
| 
 | ||||
|         formattedAttempt.timecreated = attempt.timecreated * 1000; // Convert to milliseconds.
 | ||||
|         formattedAttempt.timemodified = attempt.timemodified * 1000; // Convert to milliseconds.
 | ||||
|         formattedAttempt.success = typeof formattedAttempt.success == 'undefined' ? null : formattedAttempt.success; | ||||
| 
 | ||||
|         if (!attempt.duration) { | ||||
|             formattedAttempt.durationReadable = '-'; | ||||
|             formattedAttempt.durationCompact = '-'; | ||||
|         } else { | ||||
|             formattedAttempt.durationReadable = CoreTimeUtils.instance.formatTime(attempt.duration); | ||||
|             formattedAttempt.durationCompact = CoreTimeUtils.instance.formatDurationShort(attempt.duration); | ||||
|         } | ||||
| 
 | ||||
|         return formattedAttempt; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Format attempt data and results. | ||||
|      * | ||||
|      * @param attempt Attempt and results to format. | ||||
|      */ | ||||
|     protected formatAttemptResults(attempt: AddonModH5PActivityWSAttemptResults): AddonModH5PActivityAttemptResults { | ||||
|         const formattedAttempt: AddonModH5PActivityAttemptResults = this.formatAttempt(attempt); | ||||
| 
 | ||||
|         for (const i in formattedAttempt.results) { | ||||
|             formattedAttempt.results[i] = this.formatResult(formattedAttempt.results[i]); | ||||
|         } | ||||
| 
 | ||||
|         return formattedAttempt; | ||||
|     } | ||||
| @ -70,6 +93,17 @@ export class AddonModH5PActivityProvider { | ||||
|         return formatted; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Format an attempt's result. | ||||
|      * | ||||
|      * @param result Result to format. | ||||
|      */ | ||||
|     protected formatResult(result: AddonModH5PActivityWSResult): AddonModH5PActivityWSResult { | ||||
|         result.timecreated = result.timecreated * 1000; // Convert to milliseconds.
 | ||||
| 
 | ||||
|         return result; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get cache key for access information WS calls. | ||||
|      * | ||||
| @ -210,6 +244,61 @@ export class AddonModH5PActivityProvider { | ||||
|         return this.getH5PActivityByField(courseId, 'id', id, forceCache, siteId); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get cache key for results WS calls. | ||||
|      * | ||||
|      * @param id Instance ID. | ||||
|      * @param attemptsIds Attempts IDs. | ||||
|      * @return Cache key. | ||||
|      */ | ||||
|     protected getResultsCacheKey(id: number, attemptsIds: number[]): string { | ||||
|         return this.getResultsCommonCacheKey(id) + ':' + JSON.stringify(attemptsIds); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get common cache key for results WS calls. | ||||
|      * | ||||
|      * @param id Instance ID. | ||||
|      * @return Cache key. | ||||
|      */ | ||||
|     protected getResultsCommonCacheKey(id: number): string { | ||||
|         return this.ROOT_CACHE_KEY + 'results:' + id; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get attempt results. | ||||
|      * | ||||
|      * @param id Activity ID. | ||||
|      * @param attemptId Attempt ID. | ||||
|      * @param options Other options. | ||||
|      * @return Promise resolved with the attempts of the user. | ||||
|      */ | ||||
|     async getResults(id: number, attemptId: number, options?: AddonModH5PActivityGetResultsOptions) | ||||
|             : Promise<AddonModH5PActivityAttemptResults> { | ||||
| 
 | ||||
|         options = options || {}; | ||||
| 
 | ||||
|         const site = await CoreSites.instance.getSite(options.siteId); | ||||
| 
 | ||||
|         const params = { | ||||
|             h5pactivityid: id, | ||||
|             attemptids: [attemptId], | ||||
|         }; | ||||
|         const preSets: CoreSiteWSPreSets = { | ||||
|             cacheKey: this.getResultsCacheKey(id, params.attemptids), | ||||
|             updateFrequency: CoreSite.FREQUENCY_SOMETIMES, | ||||
|         }; | ||||
| 
 | ||||
|         if (options.ignoreCache) { | ||||
|             preSets.getFromCache = false; | ||||
|             preSets.emergencyCache = false; | ||||
|         } | ||||
| 
 | ||||
|         const response: AddonModH5PActivityGetResultsResult = await site.read('mod_h5pactivity_get_results', params, preSets); | ||||
| 
 | ||||
|         return this.formatAttemptResults(response.attempts[0]); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get cache key for attemps WS calls. | ||||
|      * | ||||
| @ -290,6 +379,33 @@ export class AddonModH5PActivityProvider { | ||||
|         await site.invalidateWsCacheForKey(this.getH5PActivityDataCacheKey(courseId)); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Invalidates all attempts results for H5P activity. | ||||
|      * | ||||
|      * @param id Activity ID. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise resolved when the data is invalidated. | ||||
|      */ | ||||
|     async invalidateAllResults(id: number, siteId?: string): Promise<void> { | ||||
|         const site = await CoreSites.instance.getSite(siteId); | ||||
| 
 | ||||
|         await site.invalidateWsCacheForKey(this.getResultsCommonCacheKey(id)); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Invalidates results of a certain attempt for H5P activity. | ||||
|      * | ||||
|      * @param id Activity ID. | ||||
|      * @param attemptId Attempt ID. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise resolved when the data is invalidated. | ||||
|      */ | ||||
|     async invalidateAttemptResults(id: number, attemptId: number, siteId?: string): Promise<void> { | ||||
|         const site = await CoreSites.instance.getSite(siteId); | ||||
| 
 | ||||
|         await site.invalidateWsCacheForKey(this.getResultsCacheKey(id, [attemptId])); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Invalidates all users attempts for H5P activity. | ||||
|      * | ||||
| @ -416,7 +532,16 @@ export type AddonModH5PActivityGetAttemptsResult = { | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Attempts data for a user as returned by the WS. | ||||
|  * Result of WS mod_h5pactivity_get_results. | ||||
|  */ | ||||
| export type AddonModH5PActivityGetResultsResult = { | ||||
|     activityid: number; // Activity course module ID.
 | ||||
|     attempts: AddonModH5PActivityWSAttemptResults[]; // The complete attempts list.
 | ||||
|     warnings?: CoreWSExternalWarning[]; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Attempts data for a user as returned by the WS mod_h5pactivity_get_attempts. | ||||
|  */ | ||||
| export type AddonModH5PActivityWSUserAttempts = { | ||||
|     userid: number; // The user id.
 | ||||
| @ -429,7 +554,7 @@ export type AddonModH5PActivityWSUserAttempts = { | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Attempt data as returned by the WS. | ||||
|  * Attempt data as returned by the WS mod_h5pactivity_get_attempts. | ||||
|  */ | ||||
| export type AddonModH5PActivityWSAttempt = { | ||||
|     id: number; // ID of the context.
 | ||||
| @ -447,7 +572,56 @@ export type AddonModH5PActivityWSAttempt = { | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Attempts data with some calculated data. | ||||
|  * Attempt and results data as returned by the WS mod_h5pactivity_get_results. | ||||
|  */ | ||||
| export type AddonModH5PActivityWSAttemptResults = AddonModH5PActivityWSAttempt & { | ||||
|     results?: AddonModH5PActivityWSResult[]; // The results of the attempt.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Attempt result data as returned by the WS mod_h5pactivity_get_results. | ||||
|  */ | ||||
| export type AddonModH5PActivityWSResult = { | ||||
|     id: number; // ID of the context.
 | ||||
|     attemptid: number; // ID of the H5P attempt.
 | ||||
|     subcontent: string; // Subcontent identifier.
 | ||||
|     timecreated: number; // Result creation.
 | ||||
|     interactiontype: string; // Interaction type.
 | ||||
|     description: string; // Result description.
 | ||||
|     content?: string; // Result extra content.
 | ||||
|     rawscore: number; // Result score value.
 | ||||
|     maxscore: number; // Result max score.
 | ||||
|     duration?: number; // Result duration in seconds.
 | ||||
|     completion?: number; // Result completion.
 | ||||
|     success?: number; // Result success.
 | ||||
|     optionslabel?: string; // Label used for result options.
 | ||||
|     correctlabel?: string; // Label used for correct answers.
 | ||||
|     answerlabel?: string; // Label used for user answers.
 | ||||
|     track?: boolean; // If the result has valid track information.
 | ||||
|     options?: { // The statement options.
 | ||||
|         description: string; // Option description.
 | ||||
|         id: number; // Option identifier.
 | ||||
|         correctanswer: AddonModH5PActivityWSResultAnswer; // The option correct answer.
 | ||||
|         useranswer: AddonModH5PActivityWSResultAnswer; // The option user answer.
 | ||||
|     }[]; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Result answer as returned by the WS mod_h5pactivity_get_results. | ||||
|  */ | ||||
| export type AddonModH5PActivityWSResultAnswer = { | ||||
|     answer?: string; // Option text value.
 | ||||
|     correct?: boolean; // If has to be displayed as correct.
 | ||||
|     incorrect?: boolean; // If has to be displayed as incorrect.
 | ||||
|     text?: boolean; // If has to be displayed as simple text.
 | ||||
|     checked?: boolean; // If has to be displayed as a checked option.
 | ||||
|     unchecked?: boolean; // If has to be displayed as a unchecked option.
 | ||||
|     pass?: boolean; // If has to be displayed as passed.
 | ||||
|     fail?: boolean; // If has to be displayed as failed.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * User attempts data with some calculated data. | ||||
|  */ | ||||
| export type AddonModH5PActivityUserAttempts = { | ||||
|     userid: number; // The user id.
 | ||||
| @ -467,6 +641,13 @@ export type AddonModH5PActivityAttempt = AddonModH5PActivityWSAttempt & { | ||||
|     durationCompact?: string; // Duration in a "short" human readable format.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Attempt and results data with some calculated data. | ||||
|  */ | ||||
| export type AddonModH5PActivityAttemptResults = AddonModH5PActivityAttempt & { | ||||
|     results?: AddonModH5PActivityWSResult[]; // The results of the attempt.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Options to pass to getDeployedFile function. | ||||
|  */ | ||||
| @ -476,6 +657,14 @@ export type AddonModH5PActivityGetDeployedFileOptions = { | ||||
|     siteId?: string; // Site ID. If not defined, current site.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Options to pass to getResults function. | ||||
|  */ | ||||
| export type AddonModH5PActivityGetResultsOptions = { | ||||
|     ignoreCache?: boolean; // Whether to ignore cache. Will fail if offline or server down.
 | ||||
|     siteId?: string; // Site ID. If not defined, current site.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Options to pass to getAttempts function. | ||||
|  */ | ||||
|  | ||||
| @ -660,6 +660,12 @@ | ||||
|     "addon.mod_glossary.searchquery": "Search query", | ||||
|     "addon.mod_glossary.tagarea_glossary_entries": "Glossary entries", | ||||
|     "addon.mod_h5pactivity.all_attempts": "All user attempts", | ||||
|     "addon.mod_h5pactivity.answer_checked": "Answer checked", | ||||
|     "addon.mod_h5pactivity.answer_correct": "Your answer is correct", | ||||
|     "addon.mod_h5pactivity.answer_fail": "Incorrect answer", | ||||
|     "addon.mod_h5pactivity.answer_incorrect": "Your answer is incorrect", | ||||
|     "addon.mod_h5pactivity.answer_pass": "Correct answer", | ||||
|     "addon.mod_h5pactivity.attempt": "Attempt", | ||||
|     "addon.mod_h5pactivity.attempt_completion_no": "This attempt is not marked as completed", | ||||
|     "addon.mod_h5pactivity.attempt_completion_yes": "This attempt is completed", | ||||
|     "addon.mod_h5pactivity.attempt_success_fail": "Fail", | ||||
| @ -675,9 +681,16 @@ | ||||
|     "addon.mod_h5pactivity.maxscore": "Max score", | ||||
|     "addon.mod_h5pactivity.modulenameplural": "H5P", | ||||
|     "addon.mod_h5pactivity.myattempts": "My attempts", | ||||
|     "addon.mod_h5pactivity.no_compatible_track": "This interaction ({{$a}}) does not provide tracking information or the tracking provided is not compatible with the current activity version.", | ||||
|     "addon.mod_h5pactivity.offlinedisabledwarning": "You will need to be online to view the H5P package.", | ||||
|     "addon.mod_h5pactivity.outcome": "Outcome", | ||||
|     "addon.mod_h5pactivity.result_fill-in": "Fill-in text", | ||||
|     "addon.mod_h5pactivity.result_other": "Unkown interaction type", | ||||
|     "addon.mod_h5pactivity.review_my_attempts": "View my attempts", | ||||
|     "addon.mod_h5pactivity.score": "Score", | ||||
|     "addon.mod_h5pactivity.score_out_of": "{{$a.rawscore}} out of {{$a.maxscore}}", | ||||
|     "addon.mod_h5pactivity.startdate": "Start date", | ||||
|     "addon.mod_h5pactivity.totalscore": "Total score", | ||||
|     "addon.mod_h5pactivity.viewattempt": "View attempt {{$a}}", | ||||
|     "addon.mod_imscp.deploymenterror": "Content package error!", | ||||
|     "addon.mod_imscp.modulenameplural": "IMS content packages", | ||||
|  | ||||
| @ -7,9 +7,11 @@ | ||||
| 
 | ||||
| @each $color-name, $color-base, $color-contrast in get-colors($colors-dark) { | ||||
|   .fa-#{$color-name} { | ||||
|     @include darkmode() { | ||||
|       color: $color-base !important; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| [dir=rtl] .icon { | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user