MOBILE-3411 h5pactivity: Add button to download the file
parent
4bbd05bd55
commit
0196a738d7
|
@ -13,5 +13,21 @@
|
||||||
|
|
||||||
<core-course-module-description [description]="description" [component]="component" [componentId]="componentId" contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId"></core-course-module-description>
|
<core-course-module-description [description]="description" [component]="component" [componentId]="componentId" contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId"></core-course-module-description>
|
||||||
|
|
||||||
<!-- TODO -->
|
<ion-list *ngIf="deployedFile && !playing">
|
||||||
|
<ion-item text-wrap *ngIf="stateMessage">
|
||||||
|
<p >{{ stateMessage | translate }}</p>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<!-- Button to download the package. -->
|
||||||
|
<ion-item *ngIf="!downloading && needsDownload" text-wrap>
|
||||||
|
<a ion-button block (click)="downloadAndPlay($event)">{{ 'core.download' | translate }}</a>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<!-- Download progress. -->
|
||||||
|
<ion-item text-center *ngIf="downloading">
|
||||||
|
<ion-spinner></ion-spinner>
|
||||||
|
<p *ngIf="progressMessage">{{ progressMessage | translate }}</p>
|
||||||
|
<p *ngIf="percentage <= 100">{{ 'core.percentagenumber' | translate:{$a: percentage} }}</p>
|
||||||
|
</ion-item>
|
||||||
|
</ion-list>
|
||||||
</core-loading>
|
</core-loading>
|
||||||
|
|
|
@ -14,8 +14,20 @@
|
||||||
|
|
||||||
import { Component, Optional, Injector } from '@angular/core';
|
import { Component, Optional, Injector } from '@angular/core';
|
||||||
import { Content } from 'ionic-angular';
|
import { Content } from 'ionic-angular';
|
||||||
|
|
||||||
|
import { CoreApp } from '@providers/app';
|
||||||
|
import { CoreFilepool } from '@providers/filepool';
|
||||||
|
import { CoreWSExternalFile } from '@providers/ws';
|
||||||
|
import { CoreDomUtils } from '@providers/utils/dom';
|
||||||
import { CoreCourseModuleMainActivityComponent } from '@core/course/classes/main-activity-component';
|
import { CoreCourseModuleMainActivityComponent } from '@core/course/classes/main-activity-component';
|
||||||
import { AddonModH5PActivity, AddonModH5PActivityProvider, AddonModH5PActivityData } from '../../providers/h5pactivity';
|
import { CoreH5P } from '@core/h5p/providers/h5p';
|
||||||
|
import { CoreH5PDisplayOptions } from '@core/h5p/classes/core';
|
||||||
|
import { CoreH5PHelper } from '@core/h5p/classes/helper';
|
||||||
|
import { CoreConstants } from '@core/constants';
|
||||||
|
|
||||||
|
import {
|
||||||
|
AddonModH5PActivity, AddonModH5PActivityProvider, AddonModH5PActivityData, AddonModH5PActivityAccessInfo
|
||||||
|
} from '../../providers/h5pactivity';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component that displays an H5P activity entry page.
|
* Component that displays an H5P activity entry page.
|
||||||
|
@ -29,8 +41,18 @@ export class AddonModH5PActivityIndexComponent extends CoreCourseModuleMainActiv
|
||||||
moduleName = 'h5pactivity';
|
moduleName = 'h5pactivity';
|
||||||
|
|
||||||
h5pActivity: AddonModH5PActivityData; // The H5P activity object.
|
h5pActivity: AddonModH5PActivityData; // The H5P activity object.
|
||||||
|
accessInfo: AddonModH5PActivityAccessInfo; // Info about the user capabilities.
|
||||||
|
deployedFile: CoreWSExternalFile; // The H5P deployed file.
|
||||||
|
|
||||||
|
stateMessage: string; // Message about the file state.
|
||||||
|
downloading: boolean; // Whether the H5P file is being downloaded.
|
||||||
|
needsDownload: boolean; // Whether the file needs to be downloaded.
|
||||||
|
percentage: string; // Download/unzip percentage.
|
||||||
|
progressMessage: string; // Message about download/unzip.
|
||||||
|
playing: boolean; // Whether the package is being played.
|
||||||
|
|
||||||
protected fetchContentDefaultError = 'addon.mod_h5pactivity.errorgetactivity';
|
protected fetchContentDefaultError = 'addon.mod_h5pactivity.errorgetactivity';
|
||||||
|
protected displayOptions: CoreH5PDisplayOptions;
|
||||||
|
|
||||||
constructor(injector: Injector,
|
constructor(injector: Injector,
|
||||||
@Optional() protected content: Content) {
|
@Optional() protected content: Content) {
|
||||||
|
@ -66,12 +88,61 @@ export class AddonModH5PActivityIndexComponent extends CoreCourseModuleMainActiv
|
||||||
this.h5pActivity = await AddonModH5PActivity.instance.getH5PActivity(this.courseId, this.module.id);
|
this.h5pActivity = await AddonModH5PActivity.instance.getH5PActivity(this.courseId, this.module.id);
|
||||||
|
|
||||||
this.description = this.h5pActivity.intro;
|
this.description = this.h5pActivity.intro;
|
||||||
|
this.displayOptions = CoreH5PHelper.decodeDisplayOptions(this.h5pActivity.displayoptions);
|
||||||
this.dataRetrieved.emit(this.h5pActivity);
|
this.dataRetrieved.emit(this.h5pActivity);
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
this.fetchAccessInfo(),
|
||||||
|
this.fetchDeployedFileData(),
|
||||||
|
]);
|
||||||
} finally {
|
} finally {
|
||||||
this.fillContextMenu(refresh);
|
this.fillContextMenu(refresh);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch the access info and store it in the right variables.
|
||||||
|
*
|
||||||
|
* @return Promise resolved when done.
|
||||||
|
*/
|
||||||
|
protected async fetchAccessInfo(): Promise<void> {
|
||||||
|
this.accessInfo = await AddonModH5PActivity.instance.getAccessInformation(this.h5pActivity.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch the deployed file data if needed and store it in the right variables.
|
||||||
|
*
|
||||||
|
* @return Promise resolved when done.
|
||||||
|
*/
|
||||||
|
protected async fetchDeployedFileData(): Promise<void> {
|
||||||
|
if (this.h5pActivity.deployedfile) {
|
||||||
|
// File already deployed and still valid, use this one.
|
||||||
|
this.deployedFile = this.h5pActivity.deployedfile;
|
||||||
|
} else {
|
||||||
|
if (!this.h5pActivity.package || !this.h5pActivity.package[0]) {
|
||||||
|
// Shouldn't happen.
|
||||||
|
throw 'No H5P package found.';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deploy the file in the server.
|
||||||
|
this.deployedFile = await CoreH5P.instance.getTrustedH5PFile(this.h5pActivity.package[0].fileurl, this.displayOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.calculateFileStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the status of the deployed file.
|
||||||
|
*
|
||||||
|
* @return Promise resolved when done.
|
||||||
|
*/
|
||||||
|
protected async calculateFileStatus(): Promise<void> {
|
||||||
|
const state = await CoreFilepool.instance.getFileStateByUrl(this.siteId, this.deployedFile.fileurl,
|
||||||
|
this.deployedFile.timemodified);
|
||||||
|
|
||||||
|
this.showFileState(state);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Perform the invalidate content function.
|
* Perform the invalidate content function.
|
||||||
*
|
*
|
||||||
|
@ -80,4 +151,120 @@ export class AddonModH5PActivityIndexComponent extends CoreCourseModuleMainActiv
|
||||||
protected invalidateContent(): Promise<any> {
|
protected invalidateContent(): Promise<any> {
|
||||||
return AddonModH5PActivity.instance.invalidateActivityData(this.courseId);
|
return AddonModH5PActivity.instance.invalidateActivityData(this.courseId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays some data based on the state of the main file.
|
||||||
|
*
|
||||||
|
* @param state The state of the file.
|
||||||
|
*/
|
||||||
|
protected showFileState(state: string): void {
|
||||||
|
|
||||||
|
if (state == CoreConstants.OUTDATED) {
|
||||||
|
this.stateMessage = 'addon.mod_h5pactivity.filestateoutdated';
|
||||||
|
this.needsDownload = true;
|
||||||
|
} else if (state == CoreConstants.NOT_DOWNLOADED) {
|
||||||
|
this.stateMessage = 'addon.mod_h5pactivity.filestatenotdownloaded';
|
||||||
|
this.needsDownload = true;
|
||||||
|
} else if (state == CoreConstants.DOWNLOADING) {
|
||||||
|
this.stateMessage = '';
|
||||||
|
|
||||||
|
if (!this.downloading) {
|
||||||
|
// It's being downloaded right now but the view isn't tracking it. "Restore" the download.
|
||||||
|
this.downloadDeployedFile().then(() => {
|
||||||
|
this.play();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.stateMessage = '';
|
||||||
|
this.needsDownload = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Download the file and play it.
|
||||||
|
*
|
||||||
|
* @param e Click event.
|
||||||
|
* @return Promise resolved when done.
|
||||||
|
*/
|
||||||
|
async downloadAndPlay(e: MouseEvent): Promise<void> {
|
||||||
|
e && e.preventDefault();
|
||||||
|
e && e.stopPropagation();
|
||||||
|
|
||||||
|
if (!CoreApp.instance.isOnline()) {
|
||||||
|
CoreDomUtils.instance.showErrorModal('core.networkerrormsg', true);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Confirm the download if needed.
|
||||||
|
await CoreDomUtils.instance.confirmDownloadSize({ size: this.deployedFile.filesize, total: true });
|
||||||
|
|
||||||
|
await this.downloadDeployedFile();
|
||||||
|
|
||||||
|
if (!this.isDestroyed) {
|
||||||
|
this.play();
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
if (CoreDomUtils.instance.isCanceledError(error) || this.isDestroyed) {
|
||||||
|
// User cancelled or view destroyed, stop.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CoreDomUtils.instance.showErrorModalDefault(error, 'core.errordownloading', true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Download athe H5P deployed file or restores an ongoing download.
|
||||||
|
*
|
||||||
|
* @return Promise resolved when done.
|
||||||
|
*/
|
||||||
|
protected async downloadDeployedFile(): Promise<void> {
|
||||||
|
this.downloading = true;
|
||||||
|
this.progressMessage = 'core.downloading';
|
||||||
|
|
||||||
|
try {
|
||||||
|
await CoreFilepool.instance.downloadUrl(this.siteId, this.deployedFile.fileurl, false, this.component, this.componentId,
|
||||||
|
this.deployedFile.timemodified, (data) => {
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.message) {
|
||||||
|
// Show a message.
|
||||||
|
this.progressMessage = data.message;
|
||||||
|
this.percentage = undefined;
|
||||||
|
} else if (typeof data.loaded != 'undefined') {
|
||||||
|
if (this.progressMessage == 'core.downloading') {
|
||||||
|
// Downloading package.
|
||||||
|
this.percentage = (Number(data.loaded / this.deployedFile.filesize) * 100).toFixed(1);
|
||||||
|
} else if (typeof data.total != 'undefined') {
|
||||||
|
// Unzipping package.
|
||||||
|
this.percentage = (Number(data.loaded / data.total) * 100).toFixed(1);
|
||||||
|
} else {
|
||||||
|
this.percentage = undefined;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.percentage = undefined;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
this.progressMessage = undefined;
|
||||||
|
this.percentage = undefined;
|
||||||
|
this.downloading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Play the package.
|
||||||
|
*/
|
||||||
|
play(): void {
|
||||||
|
this.playing = true;
|
||||||
|
|
||||||
|
// @TODO
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
{
|
{
|
||||||
"errorgetactivity": "Error getting H5P activity data.",
|
"errorgetactivity": "Error getting H5P activity data.",
|
||||||
"modulenameplural": "H5P"
|
"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.",
|
||||||
|
"modulenameplural": "H5P",
|
||||||
|
"storingfiles": "Storing files"
|
||||||
}
|
}
|
|
@ -17,6 +17,7 @@ import { Injectable } from '@angular/core';
|
||||||
import { CoreSites } from '@providers/sites';
|
import { CoreSites } from '@providers/sites';
|
||||||
import { CoreWSExternalWarning, CoreWSExternalFile } from '@providers/ws';
|
import { CoreWSExternalWarning, CoreWSExternalFile } from '@providers/ws';
|
||||||
import { CoreSite, CoreSiteWSPreSets } from '@classes/site';
|
import { CoreSite, CoreSiteWSPreSets } from '@classes/site';
|
||||||
|
import { CoreCourseLogHelper } from '@core/course/providers/log-helper';
|
||||||
|
|
||||||
import { makeSingleton, Translate } from '@singletons/core.singletons';
|
import { makeSingleton, Translate } from '@singletons/core.singletons';
|
||||||
|
|
||||||
|
@ -29,6 +30,39 @@ export class AddonModH5PActivityProvider {
|
||||||
|
|
||||||
protected ROOT_CACHE_KEY = 'mmaModH5PActivity:';
|
protected ROOT_CACHE_KEY = 'mmaModH5PActivity:';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get cache key for access information WS calls.
|
||||||
|
*
|
||||||
|
* @param id H5P activity ID.
|
||||||
|
* @return Cache key.
|
||||||
|
*/
|
||||||
|
protected getAccessInformationCacheKey(id: number): string {
|
||||||
|
return this.ROOT_CACHE_KEY + 'accessInfo:' + id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get access information for a given H5P activity.
|
||||||
|
*
|
||||||
|
* @param id H5P activity ID.
|
||||||
|
* @param forceCache True to always get the value from cache. false otherwise.
|
||||||
|
* @param siteId Site ID. If not defined, current site.
|
||||||
|
* @return Promise resolved with the data.
|
||||||
|
*/
|
||||||
|
async getAccessInformation(id: number, forceCache?: boolean, siteId?: string): Promise<AddonModH5PActivityAccessInfo> {
|
||||||
|
|
||||||
|
const site = await CoreSites.instance.getSite(siteId);
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
h5pactivityid: id,
|
||||||
|
};
|
||||||
|
const preSets = {
|
||||||
|
cacheKey: this.getAccessInformationCacheKey(id),
|
||||||
|
omitExpires: forceCache,
|
||||||
|
};
|
||||||
|
|
||||||
|
return site.read('mod_h5pactivity_get_h5pactivity_access_information', params, preSets);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get cache key for H5P activity data WS calls.
|
* Get cache key for H5P activity data WS calls.
|
||||||
*
|
*
|
||||||
|
@ -54,6 +88,7 @@ export class AddonModH5PActivityProvider {
|
||||||
: Promise<AddonModH5PActivityData> {
|
: Promise<AddonModH5PActivityData> {
|
||||||
|
|
||||||
const site = await CoreSites.instance.getSite(siteId);
|
const site = await CoreSites.instance.getSite(siteId);
|
||||||
|
|
||||||
const params = {
|
const params = {
|
||||||
courseids: [courseId],
|
courseids: [courseId],
|
||||||
};
|
};
|
||||||
|
@ -66,7 +101,7 @@ export class AddonModH5PActivityProvider {
|
||||||
preSets.omitExpires = true;
|
preSets.omitExpires = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const response: AddonModH5PActivityGetByCoursesRresult =
|
const response: AddonModH5PActivityGetByCoursesResult =
|
||||||
await site.read('mod_h5pactivity_get_h5pactivities_by_courses', params, preSets);
|
await site.read('mod_h5pactivity_get_h5pactivities_by_courses', params, preSets);
|
||||||
|
|
||||||
if (response && response.h5pactivities) {
|
if (response && response.h5pactivities) {
|
||||||
|
@ -108,6 +143,20 @@ export class AddonModH5PActivityProvider {
|
||||||
return this.getH5PActivityByField(courseId, 'id', id, forceCache, siteId);
|
return this.getH5PActivityByField(courseId, 'id', id, forceCache, siteId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalidates access information.
|
||||||
|
*
|
||||||
|
* @param id H5P activity ID.
|
||||||
|
* @param siteId Site ID. If not defined, current site.
|
||||||
|
* @return Promise resolved when the data is invalidated.
|
||||||
|
*/
|
||||||
|
async invalidateAccessInformation(id: number, siteId?: string): Promise<void> {
|
||||||
|
|
||||||
|
const site = await CoreSites.instance.getSite(siteId);
|
||||||
|
|
||||||
|
await site.invalidateWsCacheForKey(this.getAccessInformationCacheKey(id));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invalidates H5P activity data.
|
* Invalidates H5P activity data.
|
||||||
*
|
*
|
||||||
|
@ -115,10 +164,10 @@ export class AddonModH5PActivityProvider {
|
||||||
* @param siteId Site ID. If not defined, current site.
|
* @param siteId Site ID. If not defined, current site.
|
||||||
* @return Promise resolved when the data is invalidated.
|
* @return Promise resolved when the data is invalidated.
|
||||||
*/
|
*/
|
||||||
async invalidateActivityData(courseId: number, siteId?: string): Promise<any> {
|
async invalidateActivityData(courseId: number, siteId?: string): Promise<void> {
|
||||||
const site = await CoreSites.instance.getSite(siteId);
|
const site = await CoreSites.instance.getSite(siteId);
|
||||||
|
|
||||||
return site.invalidateWsCacheForKey(this.getH5PActivityDataCacheKey(courseId));
|
await site.invalidateWsCacheForKey(this.getH5PActivityDataCacheKey(courseId));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -131,6 +180,35 @@ export class AddonModH5PActivityProvider {
|
||||||
|
|
||||||
return site.wsAvailable('mod_h5pactivity_get_h5pactivities_by_courses');
|
return site.wsAvailable('mod_h5pactivity_get_h5pactivities_by_courses');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Report an H5P activity as being viewed.
|
||||||
|
*
|
||||||
|
* @param id H5P activity ID.
|
||||||
|
* @param name Name of the activity.
|
||||||
|
* @param siteId Site ID. If not defined, current site.
|
||||||
|
* @return Promise resolved when the WS call is successful.
|
||||||
|
*/
|
||||||
|
async logView(id: number, name?: string, siteId?: string): Promise<void> {
|
||||||
|
const params = {
|
||||||
|
h5pactivityid: id,
|
||||||
|
};
|
||||||
|
|
||||||
|
const result: AddonModH5PActivityViewResult = await CoreCourseLogHelper.instance.logSingle(
|
||||||
|
'mod_h5pactivity_view_h5pactivity',
|
||||||
|
params,
|
||||||
|
AddonModH5PActivityProvider.COMPONENT,
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
'h5pactivity',
|
||||||
|
{},
|
||||||
|
siteId
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!result.status) {
|
||||||
|
throw result.warnings[0] || 'Error marking H5P activity as viewed.';
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AddonModH5PActivity extends makeSingleton(AddonModH5PActivityProvider) {}
|
export class AddonModH5PActivity extends makeSingleton(AddonModH5PActivityProvider) {}
|
||||||
|
@ -167,7 +245,26 @@ export type AddonModH5PActivityData = {
|
||||||
/**
|
/**
|
||||||
* Result of WS mod_h5pactivity_get_h5pactivities_by_courses.
|
* Result of WS mod_h5pactivity_get_h5pactivities_by_courses.
|
||||||
*/
|
*/
|
||||||
export type AddonModH5PActivityGetByCoursesRresult = {
|
export type AddonModH5PActivityGetByCoursesResult = {
|
||||||
h5pactivities: AddonModH5PActivityData[];
|
h5pactivities: AddonModH5PActivityData[];
|
||||||
warnings?: CoreWSExternalWarning[];
|
warnings?: CoreWSExternalWarning[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Result of WS mod_h5pactivity_get_h5pactivity_access_information.
|
||||||
|
*/
|
||||||
|
export type AddonModH5PActivityAccessInfo = {
|
||||||
|
warnings?: CoreWSExternalWarning[];
|
||||||
|
canview?: boolean; // Whether the user has the capability mod/h5pactivity:view allowed.
|
||||||
|
canaddinstance?: boolean; // Whether the user has the capability mod/h5pactivity:addinstance allowed.
|
||||||
|
cansubmit?: boolean; // Whether the user has the capability mod/h5pactivity:submit allowed.
|
||||||
|
canreviewattempts?: boolean; // Whether the user has the capability mod/h5pactivity:reviewattempts allowed.
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Result of WS mod_h5pactivity_view_h5pactivity.
|
||||||
|
*/
|
||||||
|
export type AddonModH5PActivityViewResult = {
|
||||||
|
status: boolean; // Status: true if success.
|
||||||
|
warnings?: CoreWSExternalWarning[];
|
||||||
|
};
|
||||||
|
|
|
@ -1257,7 +1257,7 @@ export class AddonModScormProvider {
|
||||||
/**
|
/**
|
||||||
* Invalidates access information.
|
* Invalidates access information.
|
||||||
*
|
*
|
||||||
* @param forumId SCORM ID.
|
* @param scormId SCORM ID.
|
||||||
* @param siteId Site ID. If not defined, current site.
|
* @param siteId Site ID. If not defined, current site.
|
||||||
* @return Promise resolved when the data is invalidated.
|
* @return Promise resolved when the data is invalidated.
|
||||||
*/
|
*/
|
||||||
|
@ -1544,7 +1544,7 @@ export class AddonModScormProvider {
|
||||||
|
|
||||||
return this.logHelper.logSingle('mod_scorm_view_scorm', params, AddonModScormProvider.COMPONENT, id, name, 'scorm', {},
|
return this.logHelper.logSingle('mod_scorm_view_scorm', params, AddonModScormProvider.COMPONENT, id, name, 'scorm', {},
|
||||||
siteId);
|
siteId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Saves a SCORM tracking record.
|
* Saves a SCORM tracking record.
|
||||||
|
|
|
@ -660,7 +660,10 @@
|
||||||
"addon.mod_glossary.searchquery": "Search query",
|
"addon.mod_glossary.searchquery": "Search query",
|
||||||
"addon.mod_glossary.tagarea_glossary_entries": "Glossary entries",
|
"addon.mod_glossary.tagarea_glossary_entries": "Glossary entries",
|
||||||
"addon.mod_h5pactivity.errorgetactivity": "Error getting H5P activity data.",
|
"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.modulenameplural": "H5P",
|
"addon.mod_h5pactivity.modulenameplural": "H5P",
|
||||||
|
"addon.mod_h5pactivity.storingfiles": "Storing files",
|
||||||
"addon.mod_imscp.deploymenterror": "Content package error!",
|
"addon.mod_imscp.deploymenterror": "Content package error!",
|
||||||
"addon.mod_imscp.modulenameplural": "IMS content packages",
|
"addon.mod_imscp.modulenameplural": "IMS content packages",
|
||||||
"addon.mod_imscp.showmoduledescription": "Show description",
|
"addon.mod_imscp.showmoduledescription": "Show description",
|
||||||
|
|
|
@ -20,6 +20,8 @@ import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||||
import { CoreAppProvider } from '@providers/app';
|
import { CoreAppProvider } from '@providers/app';
|
||||||
import { CorePushNotificationsProvider } from '@core/pushnotifications/providers/pushnotifications';
|
import { CorePushNotificationsProvider } from '@core/pushnotifications/providers/pushnotifications';
|
||||||
|
|
||||||
|
import { makeSingleton } from '@singletons/core.singletons';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper to manage logging to Moodle.
|
* Helper to manage logging to Moodle.
|
||||||
*/
|
*/
|
||||||
|
@ -355,3 +357,5 @@ export class CoreCourseLogHelperProvider {
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class CoreCourseLogHelper extends makeSingleton(CoreCourseLogHelperProvider) {}
|
||||||
|
|
|
@ -16,8 +16,9 @@ import { CoreFile, CoreFileProvider } from '@providers/file';
|
||||||
import { CoreSites } from '@providers/sites';
|
import { CoreSites } from '@providers/sites';
|
||||||
import { CoreMimetypeUtils } from '@providers/utils/mimetype';
|
import { CoreMimetypeUtils } from '@providers/utils/mimetype';
|
||||||
import { CoreTextUtils } from '@providers/utils/text';
|
import { CoreTextUtils } from '@providers/utils/text';
|
||||||
|
import { CoreUtils } from '@providers/utils/utils';
|
||||||
import { CoreH5P } from '../providers/h5p';
|
import { CoreH5P } from '../providers/h5p';
|
||||||
import { CoreH5PCore } from './core';
|
import { CoreH5PCore, CoreH5PDisplayOptions } from './core';
|
||||||
import { FileEntry } from '@ionic-native/file';
|
import { FileEntry } from '@ionic-native/file';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -25,6 +26,25 @@ import { FileEntry } from '@ionic-native/file';
|
||||||
*/
|
*/
|
||||||
export class CoreH5PHelper {
|
export class CoreH5PHelper {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert the number representation of display options into an object.
|
||||||
|
*
|
||||||
|
* @param displayOptions Number representing display options.
|
||||||
|
* @return Object with display options.
|
||||||
|
*/
|
||||||
|
static decodeDisplayOptions(displayOptions: number): CoreH5PDisplayOptions {
|
||||||
|
const config: any = {};
|
||||||
|
const displayOptionsObject = CoreH5P.instance.h5pCore.getDisplayOptionsAsObject(displayOptions);
|
||||||
|
|
||||||
|
config.export = 0; // Don't allow downloading in the app.
|
||||||
|
config.embed = CoreUtils.instance.notNullOrUndefined(displayOptionsObject[CoreH5PCore.DISPLAY_OPTION_EMBED]) ?
|
||||||
|
displayOptionsObject[CoreH5PCore.DISPLAY_OPTION_EMBED] : 0;
|
||||||
|
config.copyright = CoreUtils.instance.notNullOrUndefined(displayOptionsObject[CoreH5PCore.DISPLAY_OPTION_COPYRIGHT]) ?
|
||||||
|
displayOptionsObject[CoreH5PCore.DISPLAY_OPTION_COPYRIGHT] : 0;
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the core H5P assets, including all core H5P JavaScript and CSS.
|
* Get the core H5P assets, including all core H5P JavaScript and CSS.
|
||||||
*
|
*
|
||||||
|
@ -107,19 +127,25 @@ export class CoreH5PHelper {
|
||||||
* @param fileUrl The file URL used to download the file.
|
* @param fileUrl The file URL used to download the file.
|
||||||
* @param file The file entry of the downloaded file.
|
* @param file The file entry of the downloaded file.
|
||||||
* @param siteId Site ID. If not defined, current site.
|
* @param siteId Site ID. If not defined, current site.
|
||||||
|
* @param onProgress Function to call on progress.
|
||||||
* @return Promise resolved when done.
|
* @return Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
static async saveH5P(fileUrl: string, file: FileEntry, siteId?: string): Promise<void> {
|
static async saveH5P(fileUrl: string, file: FileEntry, siteId?: string, onProgress?: (event: any) => any): Promise<void> {
|
||||||
siteId = siteId || CoreSites.instance.getCurrentSiteId();
|
siteId = siteId || CoreSites.instance.getCurrentSiteId();
|
||||||
|
|
||||||
// Unzip the file.
|
|
||||||
const folderName = CoreMimetypeUtils.instance.removeExtension(file.name);
|
const folderName = CoreMimetypeUtils.instance.removeExtension(file.name);
|
||||||
const destFolder = CoreTextUtils.instance.concatenatePaths(CoreFileProvider.TMPFOLDER, 'h5p/' + folderName);
|
const destFolder = CoreTextUtils.instance.concatenatePaths(CoreFileProvider.TMPFOLDER, 'h5p/' + folderName);
|
||||||
|
|
||||||
|
// Notify that the unzip is starting.
|
||||||
|
onProgress && onProgress({message: 'core.unzipping'});
|
||||||
|
|
||||||
// Unzip the file.
|
// Unzip the file.
|
||||||
await CoreFile.instance.unzipFile(file.toURL(), destFolder);
|
await CoreFile.instance.unzipFile(file.toURL(), destFolder, onProgress);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Notify that the unzip is starting.
|
||||||
|
onProgress && onProgress({message: 'addon.mod_h5pactivity.storingfiles'});
|
||||||
|
|
||||||
// Read the contents of the unzipped dir, process them and store them.
|
// Read the contents of the unzipped dir, process them and store them.
|
||||||
const contents = await CoreFile.instance.getDirectoryContents(destFolder);
|
const contents = await CoreFile.instance.getDirectoryContents(destFolder);
|
||||||
|
|
||||||
|
|
|
@ -219,11 +219,11 @@ export class CoreH5PPlayer {
|
||||||
* Get the content index file.
|
* Get the content index file.
|
||||||
*
|
*
|
||||||
* @param fileUrl URL of the H5P package.
|
* @param fileUrl URL of the H5P package.
|
||||||
* @param urlParams URL params.
|
* @param displayOptions Display options.
|
||||||
* @param siteId The site ID. If not defined, current site.
|
* @param siteId The site ID. If not defined, current site.
|
||||||
* @return Promise resolved with the file URL if exists, rejected otherwise.
|
* @return Promise resolved with the file URL if exists, rejected otherwise.
|
||||||
*/
|
*/
|
||||||
async getContentIndexFileUrl(fileUrl: string, urlParams?: {[name: string]: string}, siteId?: string): Promise<string> {
|
async getContentIndexFileUrl(fileUrl: string, displayOptions?: CoreH5PDisplayOptions, siteId?: string): Promise<string> {
|
||||||
siteId = siteId || CoreSites.instance.getCurrentSiteId();
|
siteId = siteId || CoreSites.instance.getCurrentSiteId();
|
||||||
|
|
||||||
const path = await this.h5pCore.h5pFS.getContentIndexFileUrl(fileUrl, siteId);
|
const path = await this.h5pCore.h5pFS.getContentIndexFileUrl(fileUrl, siteId);
|
||||||
|
@ -231,9 +231,9 @@ export class CoreH5PPlayer {
|
||||||
// Add display options to the URL.
|
// Add display options to the URL.
|
||||||
const data = await this.h5pCore.h5pFramework.getContentDataByUrl(fileUrl, siteId);
|
const data = await this.h5pCore.h5pFramework.getContentDataByUrl(fileUrl, siteId);
|
||||||
|
|
||||||
const options = this.h5pCore.fixDisplayOptions(this.getDisplayOptionsFromUrlParams(urlParams), data.id);
|
displayOptions = this.h5pCore.fixDisplayOptions(displayOptions, data.id);
|
||||||
|
|
||||||
return CoreUrlUtils.instance.addParamsToUrl(path, options, undefined, true);
|
return CoreUrlUtils.instance.addParamsToUrl(path, displayOptions, undefined, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -93,11 +93,12 @@ export class CoreH5PPlayerComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
|
|
||||||
let localUrl: string;
|
let localUrl: string;
|
||||||
|
const displayOptions = CoreH5P.instance.h5pPlayer.getDisplayOptionsFromUrlParams(this.urlParams);
|
||||||
|
|
||||||
if (this.canDownload && CoreFileHelper.instance.isStateDownloaded(this.state)) {
|
if (this.canDownload && CoreFileHelper.instance.isStateDownloaded(this.state)) {
|
||||||
// Package is downloaded, use the local URL.
|
// Package is downloaded, use the local URL.
|
||||||
try {
|
try {
|
||||||
localUrl = await CoreH5P.instance.h5pPlayer.getContentIndexFileUrl(this.urlParams.url, this.urlParams, this.siteId);
|
localUrl = await CoreH5P.instance.h5pPlayer.getContentIndexFileUrl(this.urlParams.url, displayOptions, this.siteId);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Index file doesn't exist, probably deleted because a lib was updated. Try to create it again.
|
// Index file doesn't exist, probably deleted because a lib was updated. Try to create it again.
|
||||||
try {
|
try {
|
||||||
|
@ -108,7 +109,7 @@ export class CoreH5PPlayerComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
await CoreH5PHelper.saveH5P(this.urlParams.url, file, this.siteId);
|
await CoreH5PHelper.saveH5P(this.urlParams.url, file, this.siteId);
|
||||||
|
|
||||||
// File treated. Try to get the index file URL again.
|
// File treated. Try to get the index file URL again.
|
||||||
localUrl = await CoreH5P.instance.h5pPlayer.getContentIndexFileUrl(this.urlParams.url, this.urlParams,
|
localUrl = await CoreH5P.instance.h5pPlayer.getContentIndexFileUrl(this.urlParams.url, displayOptions,
|
||||||
this.siteId);
|
this.siteId);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Still failing. Delete the H5P package?
|
// Still failing. Delete the H5P package?
|
||||||
|
|
|
@ -18,6 +18,7 @@ import { CoreMimetypeUtils } from '@providers/utils/mimetype';
|
||||||
import { CoreUrlUtils } from '@providers/utils/url';
|
import { CoreUrlUtils } from '@providers/utils/url';
|
||||||
import { CoreUtils } from '@providers/utils/utils';
|
import { CoreUtils } from '@providers/utils/utils';
|
||||||
import { CoreH5P } from './h5p';
|
import { CoreH5P } from './h5p';
|
||||||
|
import { CoreSites } from '@providers/sites';
|
||||||
import { CoreWSExternalFile } from '@providers/ws';
|
import { CoreWSExternalFile } from '@providers/ws';
|
||||||
import { FileEntry } from '@ionic-native/file';
|
import { FileEntry } from '@ionic-native/file';
|
||||||
import { Translate } from '@singletons/core.singletons';
|
import { Translate } from '@singletons/core.singletons';
|
||||||
|
@ -50,7 +51,14 @@ export class CoreH5PPluginFileHandler implements CorePluginFileHandler {
|
||||||
* @param siteId Site ID. If not defined, current site.
|
* @param siteId Site ID. If not defined, current site.
|
||||||
* @return Promise resolved with the file to use. Rejected if cannot download.
|
* @return Promise resolved with the file to use. Rejected if cannot download.
|
||||||
*/
|
*/
|
||||||
getDownloadableFile(file: CoreWSExternalFile, siteId?: string): Promise<CoreWSExternalFile> {
|
async getDownloadableFile(file: CoreWSExternalFile, siteId?: string): Promise<CoreWSExternalFile> {
|
||||||
|
const site = await CoreSites.instance.getSite(siteId);
|
||||||
|
|
||||||
|
if (site.containsUrl(file.fileurl) && file.fileurl.match(/pluginfile\.php\/[^\/]+\/core_h5p\/export\//i)) {
|
||||||
|
// It's already a deployed file, use it.
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
|
||||||
return CoreH5P.instance.getTrustedH5PFile(file.fileurl, {}, false, siteId);
|
return CoreH5P.instance.getTrustedH5PFile(file.fileurl, {}, false, siteId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,7 +93,7 @@ export class CoreH5PPluginFileHandler implements CorePluginFileHandler {
|
||||||
*/
|
*/
|
||||||
async getFileSize(file: CoreWSExternalFile, siteId?: string): Promise<number> {
|
async getFileSize(file: CoreWSExternalFile, siteId?: string): Promise<number> {
|
||||||
try {
|
try {
|
||||||
const trustedFile = await CoreH5P.instance.getTrustedH5PFile(file.fileurl, {}, false, siteId);
|
const trustedFile = await this.getDownloadableFile(file, siteId);
|
||||||
|
|
||||||
return trustedFile.filesize;
|
return trustedFile.filesize;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -145,9 +153,10 @@ export class CoreH5PPluginFileHandler implements CorePluginFileHandler {
|
||||||
* @param fileUrl The file URL used to download the file.
|
* @param fileUrl The file URL used to download the file.
|
||||||
* @param file The file entry of the downloaded file.
|
* @param file The file entry of the downloaded file.
|
||||||
* @param siteId Site ID. If not defined, current site.
|
* @param siteId Site ID. If not defined, current site.
|
||||||
|
* @param onProgress Function to call on progress.
|
||||||
* @return Promise resolved when done.
|
* @return Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
treatDownloadedFile(fileUrl: string, file: FileEntry, siteId?: string): Promise<void> {
|
treatDownloadedFile(fileUrl: string, file: FileEntry, siteId?: string, onProgress?: (event: any) => any): Promise<void> {
|
||||||
return CoreH5PHelper.saveH5P(fileUrl, file, siteId);
|
return CoreH5PHelper.saveH5P(fileUrl, file, siteId, onProgress);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1058,7 +1058,7 @@ export class CoreFilepoolProvider {
|
||||||
return this.wsProvider.downloadFile(fileUrl, filePath, addExtension, onProgress).then((entry) => {
|
return this.wsProvider.downloadFile(fileUrl, filePath, addExtension, onProgress).then((entry) => {
|
||||||
fileEntry = entry;
|
fileEntry = entry;
|
||||||
|
|
||||||
return this.pluginFileDelegate.treatDownloadedFile(fileUrl, fileEntry, siteId);
|
return this.pluginFileDelegate.treatDownloadedFile(fileUrl, fileEntry, siteId, onProgress);
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
const data: CoreFilepoolFileEntry = poolFileObject || {};
|
const data: CoreFilepoolFileEntry = poolFileObject || {};
|
||||||
|
|
||||||
|
|
|
@ -108,9 +108,10 @@ export interface CorePluginFileHandler extends CoreDelegateHandler {
|
||||||
* @param fileUrl The file URL used to download the file.
|
* @param fileUrl The file URL used to download the file.
|
||||||
* @param file The file entry of the downloaded file.
|
* @param file The file entry of the downloaded file.
|
||||||
* @param siteId Site ID. If not defined, current site.
|
* @param siteId Site ID. If not defined, current site.
|
||||||
|
* @param onProgress Function to call on progress.
|
||||||
* @return Promise resolved when done.
|
* @return Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
treatDownloadedFile?(fileUrl: string, file: FileEntry, siteId?: string): Promise<any>;
|
treatDownloadedFile?(fileUrl: string, file: FileEntry, siteId?: string, onProgress?: (event: any) => any): Promise<any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -360,13 +361,14 @@ export class CorePluginFileDelegate extends CoreDelegate {
|
||||||
* @param fileUrl The file URL used to download the file.
|
* @param fileUrl The file URL used to download the file.
|
||||||
* @param file The file entry of the downloaded file.
|
* @param file The file entry of the downloaded file.
|
||||||
* @param siteId Site ID. If not defined, current site.
|
* @param siteId Site ID. If not defined, current site.
|
||||||
|
* @param onProgress Function to call on progress.
|
||||||
* @return Promise resolved when done.
|
* @return Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
treatDownloadedFile(fileUrl: string, file: FileEntry, siteId?: string): Promise<any> {
|
treatDownloadedFile(fileUrl: string, file: FileEntry, siteId?: string, onProgress?: (event: any) => any): Promise<any> {
|
||||||
const handler = this.getHandlerForFile({fileurl: fileUrl});
|
const handler = this.getHandlerForFile({fileurl: fileUrl});
|
||||||
|
|
||||||
if (handler && handler.treatDownloadedFile) {
|
if (handler && handler.treatDownloadedFile) {
|
||||||
return handler.treatDownloadedFile(fileUrl, file, siteId);
|
return handler.treatDownloadedFile(fileUrl, file, siteId, onProgress);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
|
|
|
@ -872,6 +872,16 @@ export class CoreUtilsProvider {
|
||||||
return this.uniqueArray(array1.concat(array2), key);
|
return this.uniqueArray(array1.concat(array2), key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a value isn't null or undefined.
|
||||||
|
*
|
||||||
|
* @param value Value to check.
|
||||||
|
* @return True if not null and not undefined.
|
||||||
|
*/
|
||||||
|
notNullOrUndefined(value: any): boolean {
|
||||||
|
return typeof value != 'undefined' && value !== null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Open a file using platform specific method.
|
* Open a file using platform specific method.
|
||||||
*
|
*
|
||||||
|
|
Loading…
Reference in New Issue