MOBILE-3413 h5pactivity: Implement viewing attempt results

main
Dani Palou 2020-06-03 11:28:22 +02:00
parent 22caea43ba
commit b48a27e656
11 changed files with 621 additions and 16 deletions

View File

@ -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",

View File

@ -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}}"
}

View File

@ -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>

View File

@ -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 {}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -9,13 +9,11 @@
</ion-refresher>
<core-loading [hideUntil]="loaded">
<!-- User viewed. -->
<ion-list *ngIf="user">
<ion-item text-wrap>
<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-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>
</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>

View File

@ -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) {

View File

@ -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.durationReadable = CoreTimeUtils.instance.formatTime(attempt.duration);
formattedAttempt.durationCompact = CoreTimeUtils.instance.formatDurationShort(attempt.duration);
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.
*/

View File

@ -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",

View File

@ -7,7 +7,9 @@
@each $color-name, $color-base, $color-contrast in get-colors($colors-dark) {
.fa-#{$color-name} {
color: $color-base !important;
@include darkmode() {
color: $color-base !important;
}
}
}