Skip to content

Commit 0196a73

Browse files
committed
MOBILE-3411 h5pactivity: Add button to download the file
1 parent 4bbd05b commit 0196a73

File tree

14 files changed

+385
-27
lines changed

14 files changed

+385
-27
lines changed

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

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,21 @@
1313

1414
<core-course-module-description [description]="description" [component]="component" [componentId]="componentId" contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId"></core-course-module-description>
1515

16-
<!-- TODO -->
16+
<ion-list *ngIf="deployedFile && !playing">
17+
<ion-item text-wrap *ngIf="stateMessage">
18+
<p >{{ stateMessage | translate }}</p>
19+
</ion-item>
20+
21+
<!-- Button to download the package. -->
22+
<ion-item *ngIf="!downloading && needsDownload" text-wrap>
23+
<a ion-button block (click)="downloadAndPlay($event)">{{ 'core.download' | translate }}</a>
24+
</ion-item>
25+
26+
<!-- Download progress. -->
27+
<ion-item text-center *ngIf="downloading">
28+
<ion-spinner></ion-spinner>
29+
<p *ngIf="progressMessage">{{ progressMessage | translate }}</p>
30+
<p *ngIf="percentage <= 100">{{ 'core.percentagenumber' | translate:{$a: percentage} }}</p>
31+
</ion-item>
32+
</ion-list>
1733
</core-loading>

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

Lines changed: 188 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,20 @@
1414

1515
import { Component, Optional, Injector } from '@angular/core';
1616
import { Content } from 'ionic-angular';
17+
18+
import { CoreApp } from '@providers/app';
19+
import { CoreFilepool } from '@providers/filepool';
20+
import { CoreWSExternalFile } from '@providers/ws';
21+
import { CoreDomUtils } from '@providers/utils/dom';
1722
import { CoreCourseModuleMainActivityComponent } from '@core/course/classes/main-activity-component';
18-
import { AddonModH5PActivity, AddonModH5PActivityProvider, AddonModH5PActivityData } from '../../providers/h5pactivity';
23+
import { CoreH5P } from '@core/h5p/providers/h5p';
24+
import { CoreH5PDisplayOptions } from '@core/h5p/classes/core';
25+
import { CoreH5PHelper } from '@core/h5p/classes/helper';
26+
import { CoreConstants } from '@core/constants';
27+
28+
import {
29+
AddonModH5PActivity, AddonModH5PActivityProvider, AddonModH5PActivityData, AddonModH5PActivityAccessInfo
30+
} from '../../providers/h5pactivity';
1931

2032
/**
2133
* Component that displays an H5P activity entry page.
@@ -29,8 +41,18 @@ export class AddonModH5PActivityIndexComponent extends CoreCourseModuleMainActiv
2941
moduleName = 'h5pactivity';
3042

3143
h5pActivity: AddonModH5PActivityData; // The H5P activity object.
44+
accessInfo: AddonModH5PActivityAccessInfo; // Info about the user capabilities.
45+
deployedFile: CoreWSExternalFile; // The H5P deployed file.
46+
47+
stateMessage: string; // Message about the file state.
48+
downloading: boolean; // Whether the H5P file is being downloaded.
49+
needsDownload: boolean; // Whether the file needs to be downloaded.
50+
percentage: string; // Download/unzip percentage.
51+
progressMessage: string; // Message about download/unzip.
52+
playing: boolean; // Whether the package is being played.
3253

3354
protected fetchContentDefaultError = 'addon.mod_h5pactivity.errorgetactivity';
55+
protected displayOptions: CoreH5PDisplayOptions;
3456

3557
constructor(injector: Injector,
3658
@Optional() protected content: Content) {
@@ -66,12 +88,61 @@ export class AddonModH5PActivityIndexComponent extends CoreCourseModuleMainActiv
6688
this.h5pActivity = await AddonModH5PActivity.instance.getH5PActivity(this.courseId, this.module.id);
6789

6890
this.description = this.h5pActivity.intro;
91+
this.displayOptions = CoreH5PHelper.decodeDisplayOptions(this.h5pActivity.displayoptions);
6992
this.dataRetrieved.emit(this.h5pActivity);
93+
94+
await Promise.all([
95+
this.fetchAccessInfo(),
96+
this.fetchDeployedFileData(),
97+
]);
7098
} finally {
7199
this.fillContextMenu(refresh);
72100
}
73101
}
74102

103+
/**
104+
* Fetch the access info and store it in the right variables.
105+
*
106+
* @return Promise resolved when done.
107+
*/
108+
protected async fetchAccessInfo(): Promise<void> {
109+
this.accessInfo = await AddonModH5PActivity.instance.getAccessInformation(this.h5pActivity.id);
110+
}
111+
112+
/**
113+
* Fetch the deployed file data if needed and store it in the right variables.
114+
*
115+
* @return Promise resolved when done.
116+
*/
117+
protected async fetchDeployedFileData(): Promise<void> {
118+
if (this.h5pActivity.deployedfile) {
119+
// File already deployed and still valid, use this one.
120+
this.deployedFile = this.h5pActivity.deployedfile;
121+
} else {
122+
if (!this.h5pActivity.package || !this.h5pActivity.package[0]) {
123+
// Shouldn't happen.
124+
throw 'No H5P package found.';
125+
}
126+
127+
// Deploy the file in the server.
128+
this.deployedFile = await CoreH5P.instance.getTrustedH5PFile(this.h5pActivity.package[0].fileurl, this.displayOptions);
129+
}
130+
131+
await this.calculateFileStatus();
132+
}
133+
134+
/**
135+
* Calculate the status of the deployed file.
136+
*
137+
* @return Promise resolved when done.
138+
*/
139+
protected async calculateFileStatus(): Promise<void> {
140+
const state = await CoreFilepool.instance.getFileStateByUrl(this.siteId, this.deployedFile.fileurl,
141+
this.deployedFile.timemodified);
142+
143+
this.showFileState(state);
144+
}
145+
75146
/**
76147
* Perform the invalidate content function.
77148
*
@@ -80,4 +151,120 @@ export class AddonModH5PActivityIndexComponent extends CoreCourseModuleMainActiv
80151
protected invalidateContent(): Promise<any> {
81152
return AddonModH5PActivity.instance.invalidateActivityData(this.courseId);
82153
}
154+
155+
/**
156+
* Displays some data based on the state of the main file.
157+
*
158+
* @param state The state of the file.
159+
*/
160+
protected showFileState(state: string): void {
161+
162+
if (state == CoreConstants.OUTDATED) {
163+
this.stateMessage = 'addon.mod_h5pactivity.filestateoutdated';
164+
this.needsDownload = true;
165+
} else if (state == CoreConstants.NOT_DOWNLOADED) {
166+
this.stateMessage = 'addon.mod_h5pactivity.filestatenotdownloaded';
167+
this.needsDownload = true;
168+
} else if (state == CoreConstants.DOWNLOADING) {
169+
this.stateMessage = '';
170+
171+
if (!this.downloading) {
172+
// It's being downloaded right now but the view isn't tracking it. "Restore" the download.
173+
this.downloadDeployedFile().then(() => {
174+
this.play();
175+
});
176+
}
177+
} else {
178+
this.stateMessage = '';
179+
this.needsDownload = false;
180+
}
181+
}
182+
183+
/**
184+
* Download the file and play it.
185+
*
186+
* @param e Click event.
187+
* @return Promise resolved when done.
188+
*/
189+
async downloadAndPlay(e: MouseEvent): Promise<void> {
190+
e && e.preventDefault();
191+
e && e.stopPropagation();
192+
193+
if (!CoreApp.instance.isOnline()) {
194+
CoreDomUtils.instance.showErrorModal('core.networkerrormsg', true);
195+
196+
return;
197+
}
198+
199+
try {
200+
// Confirm the download if needed.
201+
await CoreDomUtils.instance.confirmDownloadSize({ size: this.deployedFile.filesize, total: true });
202+
203+
await this.downloadDeployedFile();
204+
205+
if (!this.isDestroyed) {
206+
this.play();
207+
}
208+
209+
} catch (error) {
210+
if (CoreDomUtils.instance.isCanceledError(error) || this.isDestroyed) {
211+
// User cancelled or view destroyed, stop.
212+
return;
213+
}
214+
215+
CoreDomUtils.instance.showErrorModalDefault(error, 'core.errordownloading', true);
216+
}
217+
}
218+
219+
/**
220+
* Download athe H5P deployed file or restores an ongoing download.
221+
*
222+
* @return Promise resolved when done.
223+
*/
224+
protected async downloadDeployedFile(): Promise<void> {
225+
this.downloading = true;
226+
this.progressMessage = 'core.downloading';
227+
228+
try {
229+
await CoreFilepool.instance.downloadUrl(this.siteId, this.deployedFile.fileurl, false, this.component, this.componentId,
230+
this.deployedFile.timemodified, (data) => {
231+
232+
if (!data) {
233+
return;
234+
}
235+
236+
if (data.message) {
237+
// Show a message.
238+
this.progressMessage = data.message;
239+
this.percentage = undefined;
240+
} else if (typeof data.loaded != 'undefined') {
241+
if (this.progressMessage == 'core.downloading') {
242+
// Downloading package.
243+
this.percentage = (Number(data.loaded / this.deployedFile.filesize) * 100).toFixed(1);
244+
} else if (typeof data.total != 'undefined') {
245+
// Unzipping package.
246+
this.percentage = (Number(data.loaded / data.total) * 100).toFixed(1);
247+
} else {
248+
this.percentage = undefined;
249+
}
250+
} else {
251+
this.percentage = undefined;
252+
}
253+
});
254+
255+
} finally {
256+
this.progressMessage = undefined;
257+
this.percentage = undefined;
258+
this.downloading = false;
259+
}
260+
}
261+
262+
/**
263+
* Play the package.
264+
*/
265+
play(): void {
266+
this.playing = true;
267+
268+
// @TODO
269+
}
83270
}
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
{
22
"errorgetactivity": "Error getting H5P activity data.",
3-
"modulenameplural": "H5P"
3+
"filestatenotdownloaded": "The H5P package is not downloaded. You need to download it to be able to use it.",
4+
"filestateoutdated": "The H5P package has been modified since the last download. You need to download it again to be able to use it.",
5+
"modulenameplural": "H5P",
6+
"storingfiles": "Storing files"
47
}

0 commit comments

Comments
 (0)