commit
0c45f27786
|
@ -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,6 +1,7 @@
|
|||
<!-- Buttons to add to the header. -->
|
||||
<core-navbar-buttons end>
|
||||
<core-context-menu>
|
||||
<core-context-menu-item *ngIf="h5pActivity && h5pActivity.enabletracking && accessInfo && !accessInfo.canreviewattempts" [priority]="1000" [content]="'addon.mod_h5pactivity.review_my_attempts' | translate" (action)="viewMyAttempts()" [iconAction]="'stats'"></core-context-menu-item>
|
||||
<core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate" [href]="externalUrl" [iconAction]="'open'"></core-context-menu-item>
|
||||
<core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate" (action)="expandDescription()" [iconAction]="'arrow-forward'"></core-context-menu-item>
|
||||
<core-context-menu-item *ngIf="blog" [priority]="750" content="{{'addon.blog.blog' | translate}}" [iconAction]="'fa-newspaper-o'" (action)="gotoBlog($event)"></core-context-menu-item>
|
||||
|
|
|
@ -319,6 +319,13 @@ export class AddonModH5PActivityIndexComponent extends CoreCourseModuleMainActiv
|
|||
AddonModH5PActivity.instance.logView(this.h5pActivity.id, this.h5pActivity.name, this.siteId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Go to view user events.
|
||||
*/
|
||||
viewMyAttempts(): void {
|
||||
this.navCtrl.push('AddonModH5PActivityUserAttemptsPage', {courseId: this.courseId, h5pActivityId: this.h5pActivity.id});
|
||||
}
|
||||
|
||||
/**
|
||||
* Component destroyed.
|
||||
*/
|
||||
|
|
|
@ -23,6 +23,7 @@ import { AddonModH5PActivityModuleHandler } from './providers/module-handler';
|
|||
import { AddonModH5PActivityProvider } from './providers/h5pactivity';
|
||||
import { AddonModH5PActivityPrefetchHandler } from './providers/prefetch-handler';
|
||||
import { AddonModH5PActivityIndexLinkHandler } from './providers/index-link-handler';
|
||||
import { AddonModH5PActivityReportLinkHandler } from './providers/report-link-handler';
|
||||
|
||||
// List of providers (without handlers).
|
||||
export const ADDON_MOD_H5P_ACTIVITY_PROVIDERS: any[] = [
|
||||
|
@ -40,6 +41,7 @@ export const ADDON_MOD_H5P_ACTIVITY_PROVIDERS: any[] = [
|
|||
AddonModH5PActivityModuleHandler,
|
||||
AddonModH5PActivityPrefetchHandler,
|
||||
AddonModH5PActivityIndexLinkHandler,
|
||||
AddonModH5PActivityReportLinkHandler,
|
||||
]
|
||||
})
|
||||
export class AddonModH5PActivityModule {
|
||||
|
@ -48,10 +50,12 @@ export class AddonModH5PActivityModule {
|
|||
prefetchDelegate: CoreCourseModulePrefetchDelegate,
|
||||
prefetchHandler: AddonModH5PActivityPrefetchHandler,
|
||||
linksDelegate: CoreContentLinksDelegate,
|
||||
indexHandler: AddonModH5PActivityIndexLinkHandler) {
|
||||
indexHandler: AddonModH5PActivityIndexLinkHandler,
|
||||
reportLinkHandler: AddonModH5PActivityReportLinkHandler) {
|
||||
|
||||
moduleDelegate.registerHandler(moduleHandler);
|
||||
prefetchDelegate.registerHandler(prefetchHandler);
|
||||
linksDelegate.registerHandler(indexHandler);
|
||||
linksDelegate.registerHandler(reportLinkHandler);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,35 @@
|
|||
{
|
||||
"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.",
|
||||
"attempt_success_fail": "Fail",
|
||||
"attempt_success_pass": "Pass",
|
||||
"attempt_success_unknown": "Not reported",
|
||||
"completion": "Completion",
|
||||
"downloadh5pfile": "Download H5P file",
|
||||
"duration": "Duration",
|
||||
"errorgetactivity": "Error getting H5P activity data.",
|
||||
"filestatenotdownloaded": "The H5P package is not downloaded. You need to download it to be able to use it.",
|
||||
"filestateoutdated": "The H5P package has been modified since the last download. You need to download it again to be able to use it.",
|
||||
"maxscore": "Max score",
|
||||
"modulenameplural": "H5P",
|
||||
"offlinedisabledwarning": "You will need to be online to view the H5P package."
|
||||
"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,137 @@
|
|||
// (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.getAttemptResults(this.h5pActivityId, this.attemptId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user profile.
|
||||
*
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
protected async fetchUserProfile(): Promise<void> {
|
||||
try {
|
||||
this.user = await CoreUser.instance.getProfile(this.attempt.userid, this.courseId, true);
|
||||
} catch (error) {
|
||||
// Ignore errors.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
<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">
|
||||
<!-- User viewed. -->
|
||||
<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. -->
|
||||
<ng-container *ngIf="attemptsData.scored">
|
||||
<ion-item-divider>
|
||||
<h2>{{ attemptsData.scored.title }}</h2>
|
||||
</ion-item-divider>
|
||||
<ng-container *ngTemplateOutlet="attemptsTemplate; context: {attempts: attemptsData.scored.attempts}"></ng-container>
|
||||
</ng-container>
|
||||
|
||||
<!-- All attempts. -->
|
||||
<ng-container *ngIf="attemptsData.attempts && attemptsData.attempts.length">
|
||||
<ion-item-divider>
|
||||
<h2>{{ 'addon.mod_h5pactivity.all_attempts' | translate }}</h2>
|
||||
</ion-item-divider>
|
||||
<ng-container *ngTemplateOutlet="attemptsTemplate; context: {attempts: attemptsData.attempts}"></ng-container>
|
||||
</ng-container>
|
||||
</ion-list>
|
||||
|
||||
<!-- No attempts. -->
|
||||
<core-empty-box *ngIf="attemptsData && (!attemptsData.attempts || !attemptsData.attempts.length)" icon="stats" [message]="'addon.mod_h5pactivity.attempts_none' | translate">
|
||||
</core-empty-box>
|
||||
</core-loading>
|
||||
</ion-content>
|
||||
|
||||
<!-- Template to render a list of conversations. -->
|
||||
<ng-template #attemptsTemplate let-attempts="attempts">
|
||||
<!-- "Header" of the table -->
|
||||
<ion-item text-wrap class="addon-mod_h5pactivity-table-header" detail-push>
|
||||
<ion-row align-items-center>
|
||||
<ion-col text-center>#</ion-col>
|
||||
<ion-col text-center col-5 col-md-2>{{ 'core.date' | translate }}</ion-col>
|
||||
<ion-col text-center>{{ 'addon.mod_h5pactivity.score' | translate }}</ion-col>
|
||||
<ion-col text-center class="hidden-phone">{{ 'addon.mod_h5pactivity.maxscore' | translate }}</ion-col>
|
||||
<ion-col text-center class="hidden-phone">{{ 'addon.mod_h5pactivity.duration' | translate }}</ion-col>
|
||||
<ion-col text-center class="hidden-phone">{{ 'addon.mod_h5pactivity.completion' | translate }}</ion-col>
|
||||
<ion-col text-center>{{ 'core.success' | translate }}</ion-col>
|
||||
</ion-row>
|
||||
</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" 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>
|
||||
<ion-col text-center>
|
||||
{{ attempt.rawscore }}<span class="hidden-tablet"> / {{ attempt.maxscore }}</span>
|
||||
</ion-col>
|
||||
<ion-col text-center class="hidden-phone">{{ attempt.maxscore }}</ion-col>
|
||||
<ion-col text-center class="hidden-phone">{{ attempt.durationReadable }}</ion-col>
|
||||
<ion-col text-center class="hidden-phone">
|
||||
<img *ngIf="attempt.completion" src="assets/img/completion/completion-auto-y.svg" [alt]="'addon.mod_h5pactivity.attempt_completion_yes' | translate">
|
||||
<img *ngIf="!attempt.completion" src="assets/img/completion/completion-auto-n.svg" [alt]="'addon.mod_h5pactivity.attempt_completion_no' | translate">
|
||||
</ion-col>
|
||||
<ion-col text-center class="addon-mod_h5pactivity-table-success-col">
|
||||
<core-icon *ngIf="attempt.success !== null && attempt.success" name="fa-check-circle" [label]="'addon.mod_h5pactivity.attempt_success_pass' | translate"></core-icon>
|
||||
<core-icon *ngIf="attempt.success !== null && !attempt.success" name="fa-circle-o" [label]="'addon.mod_h5pactivity.attempt_success_fail' | translate"></core-icon>
|
||||
<img *ngIf="attempt.success === null" src="assets/img/icons/empty.svg" [alt]="'addon.mod_h5pactivity.attempt_success_unknown' | translate">
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
</a>
|
||||
</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 { AddonModH5PActivityUserAttemptsPage } from './user-attempts';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AddonModH5PActivityUserAttemptsPage,
|
||||
],
|
||||
imports: [
|
||||
CoreComponentsModule,
|
||||
CoreDirectivesModule,
|
||||
CorePipesModule,
|
||||
IonicPageModule.forChild(AddonModH5PActivityUserAttemptsPage),
|
||||
TranslateModule.forChild(),
|
||||
],
|
||||
})
|
||||
export class AddonModH5PActivityUserAttemptsPageModule {}
|
|
@ -0,0 +1,37 @@
|
|||
ion-app.app-root page-addon-mod-h5pactivity-user-attempts {
|
||||
|
||||
.item.addon-mod_h5pactivity-table-header[detail-push] .item-inner {
|
||||
background-image: none;
|
||||
}
|
||||
|
||||
.item.addon-mod_h5pactivity-table-header .item-inner {
|
||||
font-size: 0.9em;
|
||||
font-weight: bold;
|
||||
|
||||
.col[text-center] {
|
||||
@include padding-horizontal(0);
|
||||
}
|
||||
}
|
||||
|
||||
.addon-mod_h5pactivity-table-header, .addon-mod_h5pactivity-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;
|
||||
}
|
||||
}
|
||||
|
||||
.addon-mod_h5pactivity-table-row {
|
||||
.addon-mod_h5pactivity-table-success-col {
|
||||
font-size: 1.4em;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
// (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 { CoreSites } from '@providers/sites';
|
||||
import { CoreDomUtils } from '@providers/utils/dom';
|
||||
import { CoreUser } from '@core/user/providers/user';
|
||||
import {
|
||||
AddonModH5PActivity, AddonModH5PActivityData, AddonModH5PActivityUserAttempts
|
||||
} from '../../providers/h5pactivity';
|
||||
|
||||
/**
|
||||
* Page that displays user attempts of a certain user.
|
||||
*/
|
||||
@IonicPage({ segment: 'addon-mod-h5pactivity-user-attempts' })
|
||||
@Component({
|
||||
selector: 'page-addon-mod-h5pactivity-user-attempts',
|
||||
templateUrl: 'user-attempts.html',
|
||||
})
|
||||
export class AddonModH5PActivityUserAttemptsPage implements OnInit {
|
||||
loaded: boolean;
|
||||
courseId: number;
|
||||
h5pActivityId: number;
|
||||
h5pActivity: AddonModH5PActivityData;
|
||||
attemptsData: AddonModH5PActivityUserAttempts;
|
||||
user: any;
|
||||
isCurrentUser: boolean;
|
||||
|
||||
protected userId: number;
|
||||
|
||||
constructor(navParams: NavParams) {
|
||||
this.courseId = navParams.get('courseId');
|
||||
this.h5pActivityId = navParams.get('h5pActivityId');
|
||||
this.userId = navParams.get('userId') || CoreSites.instance.getCurrentSiteUserId();
|
||||
this.isCurrentUser = this.userId == CoreSites.instance.getCurrentSiteUserId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Component being initialized.
|
||||
*
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
async ngOnInit(): Promise<void> {
|
||||
try {
|
||||
await this.fetchData();
|
||||
} catch (error) {
|
||||
CoreDomUtils.instance.showErrorModalDefault(error, 'Error loading attempts.');
|
||||
} 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.fetchAttempts(),
|
||||
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 fetchAttempts(): Promise<void> {
|
||||
this.attemptsData = await AddonModH5PActivity.instance.getUserAttempts(this.h5pActivityId, { userId: this.userId });
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user profile.
|
||||
*
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
protected async fetchUserProfile(): Promise<void> {
|
||||
try {
|
||||
this.user = await CoreUser.instance.getProfile(this.userId, this.courseId, true);
|
||||
} catch (error) {
|
||||
// Ignore errors.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the data.
|
||||
*
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
protected async refreshData(): Promise<void> {
|
||||
|
||||
try {
|
||||
await Promise.all([
|
||||
AddonModH5PActivity.instance.invalidateActivityData(this.courseId),
|
||||
AddonModH5PActivity.instance.invalidateUserAttempts(this.h5pActivityId, this.userId),
|
||||
]);
|
||||
} catch (error) {
|
||||
// Ignore errors.
|
||||
}
|
||||
|
||||
await this.fetchData();
|
||||
}
|
||||
}
|
|
@ -16,6 +16,8 @@ import { Injectable } from '@angular/core';
|
|||
|
||||
import { CoreSites } from '@providers/sites';
|
||||
import { CoreWSExternalWarning, CoreWSExternalFile } from '@providers/ws';
|
||||
import { CoreTimeUtils } from '@providers/utils/time';
|
||||
import { CoreUtils } from '@providers/utils/utils';
|
||||
import { CoreSite, CoreSiteWSPreSets } from '@classes/site';
|
||||
import { CoreCourseLogHelper } from '@core/course/providers/log-helper';
|
||||
import { CoreH5P } from '@core/h5p/providers/h5p';
|
||||
|
@ -32,6 +34,78 @@ export class AddonModH5PActivityProvider {
|
|||
|
||||
protected ROOT_CACHE_KEY = 'mmaModH5PActivity:';
|
||||
|
||||
/**
|
||||
* Format an attempt's data.
|
||||
*
|
||||
* @param attempt Attempt to format.
|
||||
*/
|
||||
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);
|
||||
|
||||
formattedAttempt.results = formattedAttempt.results.map((result) => {
|
||||
return this.formatResult(result);
|
||||
});
|
||||
|
||||
return formattedAttempt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the attempts of a user.
|
||||
*
|
||||
* @param data Data to format.
|
||||
* @return Formatted data.
|
||||
*/
|
||||
protected formatUserAttempts(data: AddonModH5PActivityWSUserAttempts): AddonModH5PActivityUserAttempts {
|
||||
const formatted: AddonModH5PActivityUserAttempts = data;
|
||||
|
||||
formatted.attempts = formatted.attempts.map((attempt) => {
|
||||
return this.formatAttempt(attempt);
|
||||
});
|
||||
|
||||
if (formatted.scored) {
|
||||
|
||||
formatted.scored.attempts = formatted.scored.attempts.map((attempt) => {
|
||||
return this.formatAttempt(attempt);
|
||||
});
|
||||
}
|
||||
|
||||
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.
|
||||
*
|
||||
|
@ -65,6 +139,157 @@ export class AddonModH5PActivityProvider {
|
|||
return site.read('mod_h5pactivity_get_h5pactivity_access_information', params, preSets);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get attempt results for all user attempts.
|
||||
*
|
||||
* @param id Activity ID.
|
||||
* @param options Other options.
|
||||
* @return Promise resolved with the results of the attempt.
|
||||
*/
|
||||
async getAllAttemptsResults(id: number, options?: AddonModH5PActivityGetAttemptResultsOptions)
|
||||
: Promise<AddonModH5PActivityAttemptsResults> {
|
||||
|
||||
const userAttempts = await AddonModH5PActivity.instance.getUserAttempts(id, options);
|
||||
|
||||
const attemptIds = userAttempts.attempts.map((attempt) => {
|
||||
return attempt.id;
|
||||
});
|
||||
|
||||
if (attemptIds.length) {
|
||||
// Get all the attempts with a single call.
|
||||
return AddonModH5PActivity.instance.getAttemptsResults(id, attemptIds, options);
|
||||
} else {
|
||||
// No attempts.
|
||||
return {
|
||||
activityid: id,
|
||||
attempts: [],
|
||||
warnings: [],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cache key for results WS calls.
|
||||
*
|
||||
* @param id Instance ID.
|
||||
* @param attemptsIds Attempts IDs.
|
||||
* @return Cache key.
|
||||
*/
|
||||
protected getAttemptResultsCacheKey(id: number, attemptsIds: number[]): string {
|
||||
return this.getAttemptResultsCommonCacheKey(id) + ':' + JSON.stringify(attemptsIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get common cache key for results WS calls.
|
||||
*
|
||||
* @param id Instance ID.
|
||||
* @return Cache key.
|
||||
*/
|
||||
protected getAttemptResultsCommonCacheKey(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 results of the attempt.
|
||||
*/
|
||||
async getAttemptResults(id: number, attemptId: number, options?: AddonModH5PActivityGetAttemptResultsOptions)
|
||||
: Promise<AddonModH5PActivityAttemptResults> {
|
||||
|
||||
options = options || {};
|
||||
|
||||
const site = await CoreSites.instance.getSite(options.siteId);
|
||||
|
||||
const params = {
|
||||
h5pactivityid: id,
|
||||
attemptids: [attemptId],
|
||||
};
|
||||
const preSets: CoreSiteWSPreSets = {
|
||||
cacheKey: this.getAttemptResultsCacheKey(id, params.attemptids),
|
||||
updateFrequency: CoreSite.FREQUENCY_SOMETIMES,
|
||||
};
|
||||
|
||||
if (options.forceCache) {
|
||||
preSets.omitExpires = true;
|
||||
} else if (options.ignoreCache) {
|
||||
preSets.getFromCache = false;
|
||||
preSets.emergencyCache = false;
|
||||
}
|
||||
|
||||
try {
|
||||
const response: AddonModH5PActivityGetResultsResult = await site.read('mod_h5pactivity_get_results', params, preSets);
|
||||
|
||||
if (response.warnings[0]) {
|
||||
throw response.warnings[0]; // Cannot view attempt.
|
||||
}
|
||||
|
||||
return this.formatAttemptResults(response.attempts[0]);
|
||||
} catch (error) {
|
||||
if (CoreUtils.instance.isWebServiceError(error)) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Check if the full list of results is cached. If so, get the results from there.
|
||||
options.forceCache = true;
|
||||
|
||||
const attemptsResults = await AddonModH5PActivity.instance.getAllAttemptsResults(id, options);
|
||||
|
||||
const attempt = attemptsResults.attempts.find((attempt) => {
|
||||
return attempt.id == attemptId;
|
||||
});
|
||||
|
||||
if (!attempt) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return attempt;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get attempts results.
|
||||
*
|
||||
* @param id Activity ID.
|
||||
* @param attemptsIds Attempts IDs.
|
||||
* @param options Other options.
|
||||
* @return Promise resolved with all the attempts.
|
||||
*/
|
||||
async getAttemptsResults(id: number, attemptsIds: number[], options?: AddonModH5PActivityGetAttemptResultsOptions)
|
||||
: Promise<AddonModH5PActivityAttemptsResults> {
|
||||
|
||||
options = options || {};
|
||||
|
||||
const site = await CoreSites.instance.getSite(options.siteId);
|
||||
|
||||
const params = {
|
||||
h5pactivityid: id,
|
||||
attemptids: attemptsIds,
|
||||
};
|
||||
const preSets: CoreSiteWSPreSets = {
|
||||
cacheKey: this.getAttemptResultsCommonCacheKey(id),
|
||||
updateFrequency: CoreSite.FREQUENCY_SOMETIMES,
|
||||
};
|
||||
|
||||
if (options.forceCache) {
|
||||
preSets.omitExpires = true;
|
||||
} else if (options.ignoreCache) {
|
||||
preSets.getFromCache = false;
|
||||
preSets.emergencyCache = false;
|
||||
}
|
||||
|
||||
const response: AddonModH5PActivityGetResultsResult = await site.read('mod_h5pactivity_get_results', params, preSets);
|
||||
|
||||
response.attempts = response.attempts.map((attempt) => {
|
||||
return this.formatAttemptResults(attempt);
|
||||
});
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get deployed file from an H5P activity instance.
|
||||
*
|
||||
|
@ -172,6 +397,65 @@ export class AddonModH5PActivityProvider {
|
|||
return this.getH5PActivityByField(courseId, 'id', id, forceCache, siteId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cache key for attemps WS calls.
|
||||
*
|
||||
* @param id Instance ID.
|
||||
* @param userIds User IDs.
|
||||
* @return Cache key.
|
||||
*/
|
||||
protected getUserAttemptsCacheKey(id: number, userIds: number[]): string {
|
||||
return this.getUserAttemptsCommonCacheKey(id) + ':' + JSON.stringify(userIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get common cache key for attempts WS calls.
|
||||
*
|
||||
* @param id Instance ID.
|
||||
* @return Cache key.
|
||||
*/
|
||||
protected getUserAttemptsCommonCacheKey(id: number): string {
|
||||
return this.ROOT_CACHE_KEY + 'attempts:' + id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get attempts of a certain user.
|
||||
*
|
||||
* @param id Activity ID.
|
||||
* @param options Other options.
|
||||
* @return Promise resolved with the attempts of the user.
|
||||
*/
|
||||
async getUserAttempts(id: number, options?: AddonModH5PActivityGetAttemptsOptions): Promise<AddonModH5PActivityUserAttempts> {
|
||||
|
||||
options = options || {};
|
||||
|
||||
const site = await CoreSites.instance.getSite(options.siteId);
|
||||
|
||||
const params = {
|
||||
h5pactivityid: id,
|
||||
userids: [options.userId || site.getUserId()],
|
||||
};
|
||||
const preSets: CoreSiteWSPreSets = {
|
||||
cacheKey: this.getUserAttemptsCacheKey(id, params.userids),
|
||||
updateFrequency: CoreSite.FREQUENCY_SOMETIMES,
|
||||
};
|
||||
|
||||
if (options.forceCache) {
|
||||
preSets.omitExpires = true;
|
||||
} else if (options.ignoreCache) {
|
||||
preSets.getFromCache = false;
|
||||
preSets.emergencyCache = false;
|
||||
}
|
||||
|
||||
const response: AddonModH5PActivityGetAttemptsResult = await site.read('mod_h5pactivity_get_attempts', params, preSets);
|
||||
|
||||
if (response.warnings[0]) {
|
||||
throw response.warnings[0]; // Cannot view user attempts.
|
||||
}
|
||||
|
||||
return this.formatUserAttempts(response.usersattempts[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidates access information.
|
||||
*
|
||||
|
@ -199,6 +483,62 @@ 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.getAttemptResultsCommonCacheKey(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.getAttemptResultsCacheKey(id, [attemptId]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidates all users attempts 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 invalidateAllUserAttempts(id: number, siteId?: string): Promise<void> {
|
||||
const site = await CoreSites.instance.getSite(siteId);
|
||||
|
||||
await site.invalidateWsCacheForKey(this.getUserAttemptsCommonCacheKey(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidates attempts of a certain user for H5P activity.
|
||||
*
|
||||
* @param id Activity ID.
|
||||
* @param userId User ID. If not defined, current user in the site.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved when the data is invalidated.
|
||||
*/
|
||||
async invalidateUserAttempts(id: number, userId?: number, siteId?: string): Promise<void> {
|
||||
const site = await CoreSites.instance.getSite(siteId);
|
||||
|
||||
userId = userId || site.getUserId();
|
||||
|
||||
await site.invalidateWsCacheForKey(this.getUserAttemptsCacheKey(id, [userId]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete launcher.
|
||||
*
|
||||
|
@ -286,6 +626,141 @@ export type AddonModH5PActivityAccessInfo = {
|
|||
canreviewattempts?: boolean; // Whether the user has the capability mod/h5pactivity:reviewattempts allowed.
|
||||
};
|
||||
|
||||
/**
|
||||
* Result of WS mod_h5pactivity_get_attempts.
|
||||
*/
|
||||
export type AddonModH5PActivityGetAttemptsResult = {
|
||||
activityid: number; // Activity course module ID.
|
||||
usersattempts: AddonModH5PActivityWSUserAttempts[]; // The complete users attempts list.
|
||||
warnings?: CoreWSExternalWarning[];
|
||||
};
|
||||
|
||||
/**
|
||||
* 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.
|
||||
attempts: AddonModH5PActivityWSAttempt[]; // The complete attempts list.
|
||||
scored?: { // Attempts used to grade the activity.
|
||||
title: string; // Scored attempts title.
|
||||
grademethod: string; // Scored attempts title.
|
||||
attempts: AddonModH5PActivityWSAttempt[]; // List of the grading attempts.
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Attempt data as returned by the WS mod_h5pactivity_get_attempts.
|
||||
*/
|
||||
export type AddonModH5PActivityWSAttempt = {
|
||||
id: number; // ID of the context.
|
||||
h5pactivityid: number; // ID of the H5P activity.
|
||||
userid: number; // ID of the user.
|
||||
timecreated: number; // Attempt creation.
|
||||
timemodified: number; // Attempt modified.
|
||||
attempt: number; // Attempt number.
|
||||
rawscore: number; // Attempt score value.
|
||||
maxscore: number; // Attempt max score.
|
||||
duration: number; // Attempt duration in seconds.
|
||||
completion?: number; // Attempt completion.
|
||||
success?: number; // Attempt success.
|
||||
scaled: number; // Attempt scaled.
|
||||
};
|
||||
|
||||
/**
|
||||
* 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.
|
||||
attempts: AddonModH5PActivityAttempt[]; // The complete attempts list.
|
||||
scored?: { // Attempts used to grade the activity.
|
||||
title: string; // Scored attempts title.
|
||||
grademethod: string; // Scored attempts title.
|
||||
attempts: AddonModH5PActivityAttempt[]; // List of the grading attempts.
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Attempts results with some calculated data.
|
||||
*/
|
||||
export type AddonModH5PActivityAttemptsResults = {
|
||||
activityid: number; // Activity course module ID.
|
||||
attempts: AddonModH5PActivityAttemptResults[]; // The complete attempts list.
|
||||
warnings?: CoreWSExternalWarning[];
|
||||
};
|
||||
|
||||
/**
|
||||
* Attempt with some calculated data.
|
||||
*/
|
||||
export type AddonModH5PActivityAttempt = AddonModH5PActivityWSAttempt & {
|
||||
durationReadable?: string; // Duration in a human readable format.
|
||||
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.
|
||||
*/
|
||||
|
@ -294,3 +769,18 @@ export type AddonModH5PActivityGetDeployedFileOptions = {
|
|||
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 getAttemptResults function.
|
||||
*/
|
||||
export type AddonModH5PActivityGetAttemptResultsOptions = {
|
||||
forceCache?: boolean; // Whether to force cache. If not cached, it will call the WS.
|
||||
ignoreCache?: boolean; // Whether to ignore cache. Will fail if offline or server down.
|
||||
siteId?: string; // Site ID. If not defined, current site.
|
||||
userId?: number; // User ID. If not defined, user of the site.
|
||||
};
|
||||
|
||||
/**
|
||||
* Options to pass to getAttempts function.
|
||||
*/
|
||||
export type AddonModH5PActivityGetAttemptsOptions = AddonModH5PActivityGetAttemptResultsOptions;
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Injectable, Injector } from '@angular/core';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { CoreAppProvider } from '@providers/app';
|
||||
import { CoreFilepoolProvider } from '@providers/filepool';
|
||||
|
@ -26,7 +26,7 @@ import { CoreCourseActivityPrefetchHandlerBase } from '@core/course/classes/acti
|
|||
import { CoreFilterHelperProvider } from '@core/filter/providers/helper';
|
||||
import { CoreH5PHelper } from '@core/h5p/classes/helper';
|
||||
import { CoreH5P } from '@core/h5p/providers/h5p';
|
||||
import { CoreUserProvider } from '@core/user/providers/user';
|
||||
import { CoreUser } from '@core/user/providers/user';
|
||||
import { AddonModH5PActivity, AddonModH5PActivityProvider, AddonModH5PActivityData } from './h5pactivity';
|
||||
|
||||
/**
|
||||
|
@ -47,9 +47,7 @@ export class AddonModH5PActivityPrefetchHandler extends CoreCourseActivityPrefet
|
|||
sitesProvider: CoreSitesProvider,
|
||||
domUtils: CoreDomUtilsProvider,
|
||||
filterHelper: CoreFilterHelperProvider,
|
||||
pluginFileDelegate: CorePluginFileDelegate,
|
||||
protected userProvider: CoreUserProvider,
|
||||
protected injector: Injector) {
|
||||
pluginFileDelegate: CorePluginFileDelegate) {
|
||||
|
||||
super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils, filterHelper,
|
||||
pluginFileDelegate);
|
||||
|
@ -137,7 +135,7 @@ export class AddonModH5PActivityPrefetchHandler extends CoreCourseActivityPrefet
|
|||
const introFiles = this.getIntroFilesFromInstance(module, h5pActivity);
|
||||
|
||||
await Promise.all([
|
||||
AddonModH5PActivity.instance.getAccessInformation(h5pActivity.id, true, siteId),
|
||||
this.prefetchWSData(h5pActivity, siteId),
|
||||
this.filepoolProvider.addFilesToQueue(siteId, introFiles, AddonModH5PActivityProvider.COMPONENT, module.id),
|
||||
this.prefetchMainFile(module, h5pActivity, siteId),
|
||||
]);
|
||||
|
@ -163,4 +161,31 @@ export class AddonModH5PActivityPrefetchHandler extends CoreCourseActivityPrefet
|
|||
|
||||
await this.filepoolProvider.addFilesToQueue(siteId, [deployedFile], AddonModH5PActivityProvider.COMPONENT, module.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefetch all the WebService data.
|
||||
*
|
||||
* @param h5pActivity Activity instance.
|
||||
* @param siteId Site ID.
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
protected async prefetchWSData(h5pActivity: AddonModH5PActivityData, siteId: string): Promise<void> {
|
||||
|
||||
const accessInfo = await AddonModH5PActivity.instance.getAccessInformation(h5pActivity.id, true, siteId);
|
||||
|
||||
if (!accessInfo.canreviewattempts) {
|
||||
// Not a teacher, prefetch user attempts and the current user profile.
|
||||
const site = await this.sitesProvider.getSite(siteId);
|
||||
|
||||
const options = {
|
||||
ignoreCache: true,
|
||||
siteId: siteId,
|
||||
};
|
||||
|
||||
await Promise.all([
|
||||
AddonModH5PActivity.instance.getAllAttemptsResults(h5pActivity.id, options),
|
||||
CoreUser.instance.prefetchProfiles([site.getUserId()], h5pActivity.course, siteId),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,147 @@
|
|||
// (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 { Injectable } from '@angular/core';
|
||||
import { NavController } from 'ionic-angular';
|
||||
import { CoreDomUtils } from '@providers/utils/dom';
|
||||
import { CoreContentLinksHandlerBase } from '@core/contentlinks/classes/base-handler';
|
||||
import { CoreContentLinksAction } from '@core/contentlinks/providers/delegate';
|
||||
import { CoreContentLinksHelper } from '@core/contentlinks/providers/helper';
|
||||
import { CoreCourse } from '@core/course/providers/course';
|
||||
import { AddonModH5PActivity } from './h5pactivity';
|
||||
|
||||
/**
|
||||
* Handler to treat links to H5P activity report.
|
||||
*/
|
||||
@Injectable()
|
||||
export class AddonModH5PActivityReportLinkHandler extends CoreContentLinksHandlerBase {
|
||||
name = 'AddonModH5PActivityReportLinkHandler';
|
||||
featureName = 'CoreCourseModuleDelegate_AddonModH5PActivity';
|
||||
pattern = /\/mod\/h5pactivity\/report\.php.*([\&\?]a=\d+)/;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of actions for a link (url).
|
||||
*
|
||||
* @param siteIds List of sites the URL belongs to.
|
||||
* @param url The URL to treat.
|
||||
* @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1}
|
||||
* @param courseId Course ID related to the URL. Optional but recommended.
|
||||
* @return List of (or promise resolved with list of) actions.
|
||||
*/
|
||||
getActions(siteIds: string[], url: string, params: any, courseId?: number):
|
||||
CoreContentLinksAction[] | Promise<CoreContentLinksAction[]> {
|
||||
courseId = courseId || params.courseid || params.cid;
|
||||
|
||||
return [{
|
||||
action: async (siteId, navCtrl?): Promise<void> => {
|
||||
try {
|
||||
const id = Number(params.a);
|
||||
|
||||
if (!courseId) {
|
||||
courseId = await this.getCourseId(id, siteId);
|
||||
}
|
||||
|
||||
if (typeof params.attemptid != 'undefined') {
|
||||
this.openAttemptResults(id, Number(params.attemptid), courseId, siteId, navCtrl);
|
||||
} else {
|
||||
const userId = params.userid ? Number(params.userid) : undefined;
|
||||
|
||||
this.openUserAttempts(id, courseId, siteId, userId, navCtrl);
|
||||
}
|
||||
} catch (error) {
|
||||
CoreDomUtils.instance.showErrorModalDefault(error, 'Error processing link.');
|
||||
}
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get course Id for an activity.
|
||||
*
|
||||
* @param id Activity ID.
|
||||
* @param siteId Site ID.
|
||||
* @return Promise resolved with course ID.
|
||||
*/
|
||||
protected async getCourseId(id: number, siteId: string): Promise<number> {
|
||||
const modal = CoreDomUtils.instance.showModalLoading();
|
||||
|
||||
try {
|
||||
const module = await CoreCourse.instance.getModuleBasicInfoByInstance(id, 'h5pactivity', siteId);
|
||||
|
||||
return module.course;
|
||||
} finally {
|
||||
modal.dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the handler is enabled for a certain site (site + user) and a URL.
|
||||
* If not defined, defaults to true.
|
||||
*
|
||||
* @param siteId The site ID.
|
||||
* @param url The URL to treat.
|
||||
* @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1}
|
||||
* @param courseId Course ID related to the URL. Optional but recommended.
|
||||
* @return Whether the handler is enabled for the URL and site.
|
||||
*/
|
||||
async isEnabled(siteId: string, url: string, params: any, courseId?: number): Promise<boolean> {
|
||||
return AddonModH5PActivity.instance.isPluginEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Open attempt results.
|
||||
*
|
||||
* @param id Activity ID.
|
||||
* @param attemptId Attempt ID.
|
||||
* @param courseId Course ID.
|
||||
* @param siteId Site ID.
|
||||
* @param navCtrl The NavController to use to navigate.
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
protected openAttemptResults(id: number, attemptId: number, courseId: number, siteId: string, navCtrl?: NavController): void {
|
||||
|
||||
const pageParams = {
|
||||
courseId: courseId,
|
||||
h5pActivityId: id,
|
||||
attemptId: attemptId,
|
||||
};
|
||||
|
||||
CoreContentLinksHelper.instance.goInSite(navCtrl, 'AddonModH5PActivityAttemptResultsPage', pageParams, siteId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Open user attempts.
|
||||
*
|
||||
* @param id Activity ID.
|
||||
* @param courseId Course ID.
|
||||
* @param siteId Site ID.
|
||||
* @param userId User ID. If not defined, current user in site.
|
||||
* @param navCtrl The NavController to use to navigate.
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
protected openUserAttempts(id: number, courseId: number, siteId: string, userId?: number, navCtrl?: NavController): void {
|
||||
|
||||
const pageParams = {
|
||||
courseId: courseId,
|
||||
h5pActivityId: id,
|
||||
userId: userId,
|
||||
};
|
||||
|
||||
CoreContentLinksHelper.instance.goInSite(navCtrl, 'AddonModH5PActivityUserAttemptsPage', pageParams, siteId);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
|
||||
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
|
||||
]><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" preserveAspectRatio="xMinYMid meet" overflow="visible"></svg>
|
After Width: | Height: | Size: 299 B |
|
@ -659,12 +659,39 @@
|
|||
"addon.mod_glossary.noentriesfound": "No entries were found.",
|
||||
"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",
|
||||
"addon.mod_h5pactivity.attempt_success_pass": "Pass",
|
||||
"addon.mod_h5pactivity.attempt_success_unknown": "Not reported",
|
||||
"addon.mod_h5pactivity.attempts_none": "This user has no attempts to display.",
|
||||
"addon.mod_h5pactivity.completion": "Completion",
|
||||
"addon.mod_h5pactivity.downloadh5pfile": "Download H5P file",
|
||||
"addon.mod_h5pactivity.duration": "Duration",
|
||||
"addon.mod_h5pactivity.errorgetactivity": "Error getting H5P activity data.",
|
||||
"addon.mod_h5pactivity.filestatenotdownloaded": "The H5P package is not downloaded. You need to download it to be able to use it.",
|
||||
"addon.mod_h5pactivity.filestateoutdated": "The H5P package has been modified since the last download. You need to download it again to be able to use it.",
|
||||
"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",
|
||||
"addon.mod_imscp.showmoduledescription": "Show description",
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -75,17 +75,17 @@ export class CoreIframeComponent implements OnInit, OnChanges {
|
|||
const navCtrl = this.svComponent ? this.svComponent.getMasterNav() : this.navCtrl;
|
||||
this.iframeUtils.treatFrame(iframe, false, navCtrl);
|
||||
|
||||
iframe.addEventListener('load', () => {
|
||||
this.loading = false;
|
||||
this.loaded.emit(iframe); // Notify iframe was loaded.
|
||||
});
|
||||
|
||||
iframe.addEventListener('error', () => {
|
||||
this.loading = false;
|
||||
this.domUtils.showErrorModal('core.errorloadingcontent', true);
|
||||
});
|
||||
|
||||
if (this.loading) {
|
||||
iframe.addEventListener('load', () => {
|
||||
this.loading = false;
|
||||
this.loaded.emit(iframe); // Notify iframe was loaded.
|
||||
});
|
||||
|
||||
iframe.addEventListener('error', () => {
|
||||
this.loading = false;
|
||||
this.domUtils.showErrorModal('core.errorloadingcontent', true);
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
this.loading = false;
|
||||
}, this.IFRAME_TIMEOUT);
|
||||
|
|
|
@ -32,6 +32,8 @@ import { CoreSitePluginsProvider } from '@core/siteplugins/providers/siteplugins
|
|||
import { CoreSite } from '@classes/site';
|
||||
import { CoreMainMenuProvider } from '@core/mainmenu/providers/mainmenu';
|
||||
|
||||
import { makeSingleton } from '@singletons/core.singletons';
|
||||
|
||||
/**
|
||||
* Service that provides some features regarding content links.
|
||||
*/
|
||||
|
@ -358,3 +360,5 @@ export class CoreContentLinksHelperProvider {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class CoreContentLinksHelper extends makeSingleton(CoreContentLinksHelperProvider) {}
|
||||
|
|
|
@ -22,6 +22,8 @@ import { CoreAppProvider } from '@providers/app';
|
|||
import { CoreUserOfflineProvider } from './offline';
|
||||
import { CorePushNotificationsProvider } from '@core/pushnotifications/providers/pushnotifications';
|
||||
|
||||
import { makeSingleton } from '@singletons/core.singletons';
|
||||
|
||||
/**
|
||||
* Service to provide user functionalities.
|
||||
*/
|
||||
|
@ -733,6 +735,8 @@ export class CoreUserProvider {
|
|||
}
|
||||
}
|
||||
|
||||
export class CoreUser extends makeSingleton(CoreUserProvider) {}
|
||||
|
||||
/**
|
||||
* Data returned by user_summary_exporter.
|
||||
*/
|
||||
|
|
|
@ -55,7 +55,7 @@ export class CoreIframeUtilsProvider {
|
|||
checkOnlineFrameInOffline(element: any, isSubframe?: boolean): boolean {
|
||||
const src = element.src || element.data;
|
||||
|
||||
if (src && !this.urlUtils.isLocalFileUrl(src) && !this.appProvider.isOnline()) {
|
||||
if (src && src != 'about:blank' && !this.urlUtils.isLocalFileUrl(src) && !this.appProvider.isOnline()) {
|
||||
if (element.classList.contains('core-iframe-offline-disabled')) {
|
||||
// Iframe already hidden, stop.
|
||||
return true;
|
||||
|
|
|
@ -220,7 +220,7 @@ export class CoreTimeUtilsProvider {
|
|||
* Returns hours, minutes and seconds in a human readable format.
|
||||
*
|
||||
* @param duration Duration in seconds
|
||||
* @param precision Number of elements to have in precission. 0 or undefined to full precission.
|
||||
* @param precision Number of elements to have in precision. 0 or undefined to full precission.
|
||||
* @return Duration in a human readable format.
|
||||
*/
|
||||
formatDuration(duration: number, precision?: number): string {
|
||||
|
@ -253,6 +253,29 @@ export class CoreTimeUtilsProvider {
|
|||
return durationString.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns duration in a short human readable format: minutes and seconds, in fromat: 3' 27''.
|
||||
*
|
||||
* @param duration Duration in seconds
|
||||
* @return Duration in a short human readable format.
|
||||
*/
|
||||
formatDurationShort(duration: number): string {
|
||||
|
||||
const minutes = Math.floor(duration / 60);
|
||||
const seconds = duration - minutes * 60;
|
||||
const durations = [];
|
||||
|
||||
if (minutes > 0) {
|
||||
durations.push(minutes + '\'');
|
||||
}
|
||||
|
||||
if (seconds > 0 || minutes === 0) {
|
||||
durations.push(seconds + '\'\'');
|
||||
}
|
||||
|
||||
return durations.join(' ');
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current timestamp in a "readable" format: YYYYMMDDHHmmSS.
|
||||
*
|
||||
|
|
Loading…
Reference in New Issue