Skip to content

Commit 22caea4

Browse files
committed
MOBILE-3413 h5pactivity: Implement viewing own attempts
1 parent 857f3da commit 22caea4

File tree

12 files changed

+544
-2
lines changed

12 files changed

+544
-2
lines changed

src/addon/mod/h5pactivity/components/index/addon-mod-h5pactivity-index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<!-- Buttons to add to the header. -->
22
<core-navbar-buttons end>
33
<core-context-menu>
4+
<core-context-menu-item *ngIf="h5pActivity && h5pActivity.enabletracking" [priority]="1000" [content]="'addon.mod_h5pactivity.review_my_attempts' | translate" (action)="viewMyAttempts()" [iconAction]="'stats'"></core-context-menu-item>
45
<core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate" [href]="externalUrl" [iconAction]="'open'"></core-context-menu-item>
56
<core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate" (action)="expandDescription()" [iconAction]="'arrow-forward'"></core-context-menu-item>
67
<core-context-menu-item *ngIf="blog" [priority]="750" content="{{'addon.blog.blog' | translate}}" [iconAction]="'fa-newspaper-o'" (action)="gotoBlog($event)"></core-context-menu-item>

src/addon/mod/h5pactivity/components/index/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,13 @@ export class AddonModH5PActivityIndexComponent extends CoreCourseModuleMainActiv
319319
AddonModH5PActivity.instance.logView(this.h5pActivity.id, this.h5pActivity.name, this.siteId);
320320
}
321321

322+
/**
323+
* Go to view user events.
324+
*/
325+
viewMyAttempts(): void {
326+
this.navCtrl.push('AddonModH5PActivityUserAttemptsPage', {courseId: this.courseId, h5pActivityId: this.h5pActivity.id});
327+
}
328+
322329
/**
323330
* Component destroyed.
324331
*/
Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,22 @@
11
{
2+
"all_attempts": "All user attempts",
3+
"attempt_completion_no": "This attempt is not marked as completed",
4+
"attempt_completion_yes": "This attempt is completed",
5+
"attempts_none": "This user has no attempts to display.",
6+
"attempt_success_fail": "Fail",
7+
"attempt_success_pass": "Pass",
8+
"attempt_success_unknown": "Not reported",
9+
"completion": "Completion",
210
"downloadh5pfile": "Download H5P file",
11+
"duration": "Duration",
312
"errorgetactivity": "Error getting H5P activity data.",
413
"filestatenotdownloaded": "The H5P package is not downloaded. You need to download it to be able to use it.",
514
"filestateoutdated": "The H5P package has been modified since the last download. You need to download it again to be able to use it.",
15+
"maxscore": "Max score",
616
"modulenameplural": "H5P",
7-
"offlinedisabledwarning": "You will need to be online to view the H5P package."
17+
"myattempts": "My attempts",
18+
"offlinedisabledwarning": "You will need to be online to view the H5P package.",
19+
"review_my_attempts": "View my attempts",
20+
"score": "Score",
21+
"viewattempt": "View attempt {{$a}}"
822
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
<ion-header>
2+
<ion-navbar core-back-button>
3+
<ion-title><core-format-text *ngIf="h5pActivity" [text]="h5pActivity.name" contextLevel="module" [contextInstanceId]="h5pActivity.coursemodule" [courseId]="courseId"></core-format-text></ion-title>
4+
</ion-navbar>
5+
</ion-header>
6+
<ion-content>
7+
<ion-refresher [enabled]="loaded" (ionRefresh)="doRefresh($event)">
8+
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
9+
</ion-refresher>
10+
<core-loading [hideUntil]="loaded">
11+
<!-- User viewed. -->
12+
<ion-list *ngIf="user">
13+
<ion-item text-wrap>
14+
<ion-avatar core-user-avatar [user]="user" item-start></ion-avatar>
15+
<h2 *ngIf="!isCurrentUser">{{ user.fullname }}</h2>
16+
<h2 *ngIf="isCurrentUser">{{ 'addon.mod_h5pactivity.myattempts' | translate }}</h2>
17+
</ion-item>
18+
</ion-list>
19+
20+
<ion-list *ngIf="attemptsData">
21+
<!-- Attempts used to calculate the score. -->
22+
<ng-container *ngIf="attemptsData.scored">
23+
<ion-item-divider>
24+
<h2>{{ attemptsData.scored.title }}</h2>
25+
</ion-item-divider>
26+
<ng-container *ngTemplateOutlet="attemptsTemplate; context: {attempts: attemptsData.scored.attempts}"></ng-container>
27+
</ng-container>
28+
29+
<!-- All attempts. -->
30+
<ng-container *ngIf="attemptsData.attempts && attemptsData.attempts.length">
31+
<ion-item-divider>
32+
<h2>{{ 'addon.mod_h5pactivity.all_attempts' | translate }}</h2>
33+
</ion-item-divider>
34+
<ng-container *ngTemplateOutlet="attemptsTemplate; context: {attempts: attemptsData.attempts}"></ng-container>
35+
</ng-container>
36+
</ion-list>
37+
38+
<!-- No attempts. -->
39+
<core-empty-box *ngIf="attemptsData && (!attemptsData.attempts || !attemptsData.attempts.length)" icon="stats" [message]="'addon.mod_h5pactivity.attempts_none' | translate">
40+
</core-empty-box>
41+
</core-loading>
42+
</ion-content>
43+
44+
<!-- Template to render a list of conversations. -->
45+
<ng-template #attemptsTemplate let-attempts="attempts">
46+
<!-- "Header" of the table -->
47+
<ion-item text-wrap class="addon-mod_h5pactivity-table-header" detail-push>
48+
<ion-row align-items-center>
49+
<ion-col text-center>#</ion-col>
50+
<ion-col text-center col-5 col-md-2>{{ 'core.date' | translate }}</ion-col>
51+
<ion-col text-center>{{ 'addon.mod_h5pactivity.score' | translate }}</ion-col>
52+
<ion-col text-center class="hidden-phone">{{ 'addon.mod_h5pactivity.maxscore' | translate }}</ion-col>
53+
<ion-col text-center class="hidden-phone">{{ 'addon.mod_h5pactivity.duration' | translate }}</ion-col>
54+
<ion-col text-center class="hidden-phone">{{ 'addon.mod_h5pactivity.completion' | translate }}</ion-col>
55+
<ion-col text-center>{{ 'core.success' | translate }}</ion-col>
56+
</ion-row>
57+
</ion-item>
58+
59+
<!-- List of attempts. -->
60+
<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">
61+
<ion-row align-items-center>
62+
<ion-col text-center>{{ attempt.attempt }}</ion-col>
63+
<ion-col text-center col-5 col-md-2>{{ attempt.timemodified | coreFormatDate:'strftimedatetimeshort' }}</ion-col>
64+
<ion-col text-center>
65+
{{ attempt.rawscore }}<span class="hidden-tablet"> / {{ attempt.maxscore }}</span>
66+
</ion-col>
67+
<ion-col text-center class="hidden-phone">{{ attempt.maxscore }}</ion-col>
68+
<ion-col text-center class="hidden-phone">{{ attempt.durationReadable }}</ion-col>
69+
<ion-col text-center class="hidden-phone">
70+
<img *ngIf="attempt.completion" src="assets/img/completion/completion-auto-y.svg" [alt]="'addon.mod_h5pactivity.attempt_completion_yes' | translate">
71+
<img *ngIf="!attempt.completion" src="assets/img/completion/completion-auto-n.svg" [alt]="'addon.mod_h5pactivity.attempt_completion_no' | translate">
72+
</ion-col>
73+
<ion-col text-center class="addon-mod_h5pactivity-table-success-col">
74+
<core-icon *ngIf="attempt.success !== null && attempt.success" name="fa-check-circle" [label]="'addon.mod_h5pactivity.attempt_success_pass' | translate"></core-icon>
75+
<core-icon *ngIf="attempt.success !== null && !attempt.success" name="fa-circle-o" [label]="'addon.mod_h5pactivity.attempt_success_fail' | translate"></core-icon>
76+
<img *ngIf="attempt.success === null" src="assets/img/icons/empty.svg" [alt]="'addon.mod_h5pactivity.attempt_success_unknown' | translate">
77+
</ion-col>
78+
</ion-row>
79+
</a>
80+
</ng-template>
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// (C) Copyright 2015 Moodle Pty Ltd.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import { NgModule } from '@angular/core';
16+
import { IonicPageModule } from 'ionic-angular';
17+
import { TranslateModule } from '@ngx-translate/core';
18+
import { CoreComponentsModule } from '@components/components.module';
19+
import { CoreDirectivesModule } from '@directives/directives.module';
20+
import { CorePipesModule } from '@pipes/pipes.module';
21+
import { AddonModH5PActivityUserAttemptsPage } from './user-attempts';
22+
23+
@NgModule({
24+
declarations: [
25+
AddonModH5PActivityUserAttemptsPage,
26+
],
27+
imports: [
28+
CoreComponentsModule,
29+
CoreDirectivesModule,
30+
CorePipesModule,
31+
IonicPageModule.forChild(AddonModH5PActivityUserAttemptsPage),
32+
TranslateModule.forChild(),
33+
],
34+
})
35+
export class AddonModH5PActivityUserAttemptsPageModule {}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
ion-app.app-root page-addon-mod-h5pactivity-user-attempts {
2+
3+
.item.addon-mod_h5pactivity-table-header[detail-push] .item-inner {
4+
background-image: none;
5+
}
6+
7+
.item.addon-mod_h5pactivity-table-header .item-inner {
8+
font-size: 0.9em;
9+
font-weight: bold;
10+
11+
.col[text-center] {
12+
@include padding-horizontal(0);
13+
}
14+
}
15+
16+
.addon-mod_h5pactivity-table-header, .addon-mod_h5pactivity-table-row {
17+
18+
.item-inner ion-label {
19+
@include margin(null, 0, null, null);
20+
}
21+
22+
.item {
23+
@include padding(null, null, null, 0);
24+
}
25+
26+
.label {
27+
margin-top: 0;
28+
margin-bottom: 0;
29+
}
30+
}
31+
32+
.addon-mod_h5pactivity-table-row {
33+
.addon-mod_h5pactivity-table-success-col {
34+
font-size: 1.4em;
35+
}
36+
}
37+
}
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
// (C) Copyright 2015 Moodle Pty Ltd.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import { Component, OnInit } from '@angular/core';
16+
import { IonicPage, NavParams } from 'ionic-angular';
17+
import { CoreSites } from '@providers/sites';
18+
import { CoreDomUtils } from '@providers/utils/dom';
19+
import { CoreUser } from '@core/user/providers/user';
20+
import {
21+
AddonModH5PActivity, AddonModH5PActivityData, AddonModH5PActivityUserAttempts
22+
} from '../../providers/h5pactivity';
23+
24+
/**
25+
* Page that displays user attempts of a certain user.
26+
*/
27+
@IonicPage({ segment: 'addon-mod-h5pactivity-user-attempts' })
28+
@Component({
29+
selector: 'page-addon-mod-h5pactivity-user-attempts',
30+
templateUrl: 'user-attempts.html',
31+
})
32+
export class AddonModH5PActivityUserAttemptsPage implements OnInit {
33+
loaded: boolean;
34+
h5pActivity: AddonModH5PActivityData;
35+
attemptsData: AddonModH5PActivityUserAttempts;
36+
user: any;
37+
isCurrentUser: boolean;
38+
39+
protected courseId: number;
40+
protected h5pActivityId: number;
41+
protected userId: number;
42+
43+
constructor(navParams: NavParams) {
44+
this.courseId = navParams.get('courseId');
45+
this.h5pActivityId = navParams.get('h5pActivityId');
46+
this.userId = navParams.get('userId') || CoreSites.instance.getCurrentSiteUserId();
47+
this.isCurrentUser = this.userId == CoreSites.instance.getCurrentSiteUserId();
48+
}
49+
50+
/**
51+
* Component being initialized.
52+
*
53+
* @return Promise resolved when done.
54+
*/
55+
async ngOnInit(): Promise<void> {
56+
try {
57+
await this.fetchData();
58+
} catch (error) {
59+
CoreDomUtils.instance.showErrorModalDefault(error, 'Error loading attempts.');
60+
} finally {
61+
this.loaded = true;
62+
}
63+
}
64+
65+
/**
66+
* Refresh the data.
67+
*
68+
* @param refresher Refresher.
69+
*/
70+
doRefresh(refresher: any): void {
71+
this.refreshData().finally(() => {
72+
refresher.complete();
73+
});
74+
}
75+
76+
/**
77+
* Get quiz data and attempt data.
78+
*
79+
* @return Promise resolved when done.
80+
*/
81+
protected async fetchData(): Promise<void> {
82+
await Promise.all([
83+
this.fetchActivity(),
84+
this.fetchAttempts(),
85+
this.fetchUserProfile(),
86+
]);
87+
}
88+
89+
/**
90+
* Get activity data.
91+
*
92+
* @return Promise resolved when done.
93+
*/
94+
protected async fetchActivity(): Promise<void> {
95+
this.h5pActivity = await AddonModH5PActivity.instance.getH5PActivityById(this.courseId, this.h5pActivityId);
96+
}
97+
98+
/**
99+
* Get attempts.
100+
*
101+
* @return Promise resolved when done.
102+
*/
103+
protected async fetchAttempts(): Promise<void> {
104+
this.attemptsData = await AddonModH5PActivity.instance.getUserAttempts(this.h5pActivityId, { userId: this.userId });
105+
}
106+
107+
/**
108+
* Get user profile.
109+
*
110+
* @return Promise resolved when done.
111+
*/
112+
protected async fetchUserProfile(): Promise<void> {
113+
this.user = await CoreUser.instance.getProfile(this.userId, this.courseId, true);
114+
}
115+
116+
/**
117+
* Refresh the data.
118+
*
119+
* @return Promise resolved when done.
120+
*/
121+
protected async refreshData(): Promise<void> {
122+
123+
try {
124+
await Promise.all([
125+
AddonModH5PActivity.instance.invalidateActivityData(this.courseId),
126+
AddonModH5PActivity.instance.invalidateUserAttempts(this.h5pActivityId, this.userId),
127+
]);
128+
} catch (error) {
129+
// Ignore errors.
130+
}
131+
132+
await this.fetchData();
133+
}
134+
}

0 commit comments

Comments
 (0)