MOBILE-2235 h5p: Save content and create player

main
Dani Palou 2019-11-22 11:28:32 +01:00
parent ef5f96a643
commit fb898f6f05
6 changed files with 2112 additions and 23 deletions

View File

@ -1550,7 +1550,73 @@
"core.group": "Group", "core.group": "Group",
"core.groupsseparate": "Separate groups", "core.groupsseparate": "Separate groups",
"core.groupsvisible": "Visible groups", "core.groupsvisible": "Visible groups",
"core.h5p.author": "Author",
"core.h5p.by": "by",
"core.h5p.cancellabel": "Cancel",
"core.h5p.ccattribution": "Attribution (CC BY)",
"core.h5p.ccattributionnc": "Attribution-NonCommercial (CC BY-NC)",
"core.h5p.ccattributionncnd": "Attribution-NonCommercial-NoDerivs (CC BY-NC-ND)",
"core.h5p.ccattributionncsa": "Attribution-NonCommercial-ShareAlike (CC BY-NC-SA)",
"core.h5p.ccattributionnd": "Attribution-NoDerivs (CC BY-ND)",
"core.h5p.ccattributionsa": "Attribution-ShareAlike (CC BY-SA)",
"core.h5p.changelog": "Changelog",
"core.h5p.close": "Close",
"core.h5p.confirmdialogbody": "Please confirm that you wish to proceed. This action is not reversible.",
"core.h5p.confirmdialogheader": "Confirm action",
"core.h5p.confirmlabel": "Confirm",
"core.h5p.connectionLost": "Connection lost. Results will be stored and sent when you regain connection.",
"core.h5p.connectionReestablished": "Connection reestablished.",
"core.h5p.contentCopied": "Content is copied to the clipboard",
"core.h5p.contentchanged": "This content has changed since you last used it.",
"core.h5p.contenttype": "Content Type",
"core.h5p.copyright": "Rights of use",
"core.h5p.copyrightstring": "Copyright",
"core.h5p.copyrighttitle": "View copyright information for this content.",
"core.h5p.disablefullscreen": "Disable fullscreen",
"core.h5p.download": "Download",
"core.h5p.downloadtitle": "Download this content as a H5P file.",
"core.h5p.embed": "Embed",
"core.h5p.embedtitle": "View the embed code for this content.",
"core.h5p.fullscreen": "Fullscreen",
"core.h5p.h5ptitle": "Visit H5P.org to check out more cool content.",
"core.h5p.hideadvanced": "Hide advanced",
"core.h5p.license": "License",
"core.h5p.licenseCC010": "CC0 1.0 Universal (CC0 1.0) Public Domain Dedication",
"core.h5p.licenseCC10": "1.0 Generic",
"core.h5p.licenseCC20": "2.0 Generic",
"core.h5p.licenseCC25": "2.5 Generic",
"core.h5p.licenseCC30": "3.0 Unported",
"core.h5p.licenseCC40": "4.0 International",
"core.h5p.licenseGPL": "General Public License",
"core.h5p.licenseV1": "Version 1",
"core.h5p.licenseV2": "Version 2",
"core.h5p.licenseV3": "Version 3",
"core.h5p.licenseextras": "License Extras",
"core.h5p.nocopyright": "No copyright information available for this content.",
"core.h5p.offlineDialogBody": "We were unable to send information about your completion of this task. Please check your internet connection.",
"core.h5p.offlineDialogHeader": "Your connection to the server was lost",
"core.h5p.offlineDialogRetryButtonLabel": "Retry now",
"core.h5p.offlineDialogRetryMessage": "Retrying in :num....",
"core.h5p.offlineSuccessfulSubmit": "Successfully submitted results.",
"core.h5p.pd": "Public Domain",
"core.h5p.pdm": "Public Domain Mark (PDM)",
"core.h5p.play": "Play H5P", "core.h5p.play": "Play H5P",
"core.h5p.resizescript": "Include this script on your website if you want dynamic sizing of the embedded content:",
"core.h5p.resubmitScores": "Attempting to submit stored results.",
"core.h5p.reuse": "Reuse",
"core.h5p.reuseContent": "Reuse Content",
"core.h5p.reuseDescription": "Reuse this content.",
"core.h5p.showadvanced": "Show advanced",
"core.h5p.showless": "Show less",
"core.h5p.showmore": "Show more",
"core.h5p.size": "Size",
"core.h5p.source": "Source",
"core.h5p.startingover": "You'll be starting over.",
"core.h5p.sublevel": "Sublevel",
"core.h5p.thumbnail": "Thumbnail",
"core.h5p.title": "Title",
"core.h5p.undisclosed": "Undisclosed",
"core.h5p.year": "Year",
"core.hasdatatosync": "This {{$a}} has offline data to be synchronised.", "core.hasdatatosync": "This {{$a}} has offline data to be synchronised.",
"core.help": "Help", "core.help": "Help",
"core.hide": "Hide", "core.hide": "Hide",

View File

@ -16,6 +16,7 @@ import { Component, Input, ElementRef, OnInit, OnDestroy, OnChanges, SimpleChang
import { CoreAppProvider } from '@providers/app'; import { CoreAppProvider } from '@providers/app';
import { CoreEventsProvider } from '@providers/events'; import { CoreEventsProvider } from '@providers/events';
import { CoreFilepoolProvider } from '@providers/filepool'; import { CoreFilepoolProvider } from '@providers/filepool';
import { CoreLoggerProvider } from '@providers/logger';
import { CoreSitesProvider } from '@providers/sites'; import { CoreSitesProvider } from '@providers/sites';
import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreTextUtilsProvider } from '@providers/utils/text';
@ -23,6 +24,7 @@ import { CoreUrlUtilsProvider } from '@providers/utils/url';
import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreUtilsProvider } from '@providers/utils/utils';
import { CoreH5PProvider } from '@core/h5p/providers/h5p'; import { CoreH5PProvider } from '@core/h5p/providers/h5p';
import { CorePluginFileDelegate } from '@providers/plugin-file-delegate'; import { CorePluginFileDelegate } from '@providers/plugin-file-delegate';
import { CoreConstants } from '@core/constants';
/** /**
* Component to render an H5P package. * Component to render an H5P package.
@ -47,8 +49,10 @@ export class CoreH5PPlayerComponent implements OnInit, OnChanges, OnDestroy {
protected siteCanDownload: boolean; protected siteCanDownload: boolean;
protected observer; protected observer;
protected urlParams; protected urlParams;
protected logger;
constructor(public elementRef: ElementRef, constructor(loggerProvider: CoreLoggerProvider,
public elementRef: ElementRef,
protected sitesProvider: CoreSitesProvider, protected sitesProvider: CoreSitesProvider,
protected urlUtils: CoreUrlUtilsProvider, protected urlUtils: CoreUrlUtilsProvider,
protected utils: CoreUtilsProvider, protected utils: CoreUtilsProvider,
@ -60,6 +64,7 @@ export class CoreH5PPlayerComponent implements OnInit, OnChanges, OnDestroy {
protected domUtils: CoreDomUtilsProvider, protected domUtils: CoreDomUtilsProvider,
protected pluginFileDelegate: CorePluginFileDelegate) { protected pluginFileDelegate: CorePluginFileDelegate) {
this.logger = loggerProvider.getInstance('CoreH5PPlayerComponent');
this.siteId = sitesProvider.getCurrentSiteId(); this.siteId = sitesProvider.getCurrentSiteId();
this.siteCanDownload = this.sitesProvider.getCurrentSite().canDownloadFiles(); this.siteCanDownload = this.sitesProvider.getCurrentSite().canDownloadFiles();
} }
@ -92,11 +97,30 @@ export class CoreH5PPlayerComponent implements OnInit, OnChanges, OnDestroy {
this.loading = true; this.loading = true;
// @TODO: Check if package is downloaded and use the local player if so. let promise;
// Get auto-login URL so the user is automatically authenticated. if (this.canDownload && (this.state == CoreConstants.DOWNLOADED || this.state == CoreConstants.OUTDATED)) {
this.sitesProvider.getCurrentSite().getAutoLoginUrl(this.src, false).then((url) => { // Package is downloaded, use the local URL.
promise = this.h5pProvider.getContentIndexFileUrl(this.urlParams.url).catch((error) => {
// It seems there was something wrong when creating the index file. Delete the package?
this.logger.error('Error loading downloaded index:', error, this.src);
});
} else {
promise = Promise.resolve();
}
promise.then((url) => {
if (url) {
// Local package.
this.playerSrc = url; this.playerSrc = url;
} else {
// Get auto-login URL so the user is automatically authenticated.
return this.sitesProvider.getCurrentSite().getAutoLoginUrl(this.src, false).then((url) => {
// Add the preventredirect param so the user can authenticate.
this.playerSrc = this.urlUtils.addParamsToUrl(url, {preventredirect: false});
});
}
}).finally(() => {
this.loading = false; this.loading = false;
this.showPackage = true; this.showPackage = true;
}); });

View File

@ -1,3 +1,69 @@
{ {
"play": "Play H5P" "author": "Author",
"by": "by",
"cancellabel": "Cancel",
"ccattribution": "Attribution (CC BY)",
"ccattributionnc": "Attribution-NonCommercial (CC BY-NC)",
"ccattributionncnd": "Attribution-NonCommercial-NoDerivs (CC BY-NC-ND)",
"ccattributionncsa": "Attribution-NonCommercial-ShareAlike (CC BY-NC-SA)",
"ccattributionnd": "Attribution-NoDerivs (CC BY-ND)",
"ccattributionsa": "Attribution-ShareAlike (CC BY-SA)",
"changelog": "Changelog",
"close": "Close",
"confirmdialogbody": "Please confirm that you wish to proceed. This action is not reversible.",
"confirmdialogheader": "Confirm action",
"confirmlabel": "Confirm",
"connectionLost": "Connection lost. Results will be stored and sent when you regain connection.",
"connectionReestablished": "Connection reestablished.",
"contentCopied": "Content is copied to the clipboard",
"contentchanged": "This content has changed since you last used it.",
"contenttype": "Content Type",
"copyright": "Rights of use",
"copyrightstring": "Copyright",
"copyrighttitle": "View copyright information for this content.",
"disablefullscreen": "Disable fullscreen",
"download": "Download",
"downloadtitle": "Download this content as a H5P file.",
"embed": "Embed",
"embedtitle": "View the embed code for this content.",
"fullscreen": "Fullscreen",
"h5ptitle": "Visit H5P.org to check out more cool content.",
"hideadvanced": "Hide advanced",
"license": "License",
"licenseCC010": "CC0 1.0 Universal (CC0 1.0) Public Domain Dedication",
"licenseCC10": "1.0 Generic",
"licenseCC20": "2.0 Generic",
"licenseCC25": "2.5 Generic",
"licenseCC30": "3.0 Unported",
"licenseCC40": "4.0 International",
"licenseGPL": "General Public License",
"licenseV1": "Version 1",
"licenseV2": "Version 2",
"licenseV3": "Version 3",
"licenseextras": "License Extras",
"nocopyright": "No copyright information available for this content.",
"offlineDialogBody": "We were unable to send information about your completion of this task. Please check your internet connection.",
"offlineDialogHeader": "Your connection to the server was lost",
"offlineDialogRetryButtonLabel": "Retry now",
"offlineDialogRetryMessage": "Retrying in :num....",
"offlineSuccessfulSubmit": "Successfully submitted results.",
"pd": "Public Domain",
"pdm": "Public Domain Mark (PDM)",
"play": "Play H5P",
"resizescript": "Include this script on your website if you want dynamic sizing of the embedded content:",
"resubmitScores": "Attempting to submit stored results.",
"reuse": "Reuse",
"reuseContent": "Reuse Content",
"reuseDescription": "Reuse this content.",
"showadvanced": "Show advanced",
"showless": "Show less",
"showmore": "Show more",
"size": "Size",
"source": "Source",
"startingover": "You'll be starting over.",
"sublevel": "Sublevel",
"thumbnail": "Thumbnail",
"title": "Title",
"undisclosed": "Undisclosed",
"year": "Year"
} }

File diff suppressed because it is too large Load Diff

View File

@ -13,6 +13,10 @@
// limitations under the License. // limitations under the License.
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { CoreTextUtilsProvider } from '@providers/utils/text';
import { CoreH5PContentDependencyData, CoreH5PDependencyAsset } from './h5p';
import { Md5 } from 'ts-md5/dist/md5';
/** /**
* Utils service with helper functions for H5P. * Utils service with helper functions for H5P.
@ -20,9 +24,120 @@ import { Injectable } from '@angular/core';
@Injectable() @Injectable()
export class CoreH5PUtilsProvider { export class CoreH5PUtilsProvider {
constructor() { // Map to slugify characters.
// Nothing to do. protected SLUGIFY_MAP = {
} æ: 'ae',
ø: 'oe',
ö: 'o',
ó: 'o',
ô: 'o',
Ò: 'oe',
Õ: 'o',
Ý: 'o',
ý: 'y',
ÿ: 'y',
ā: 'y',
ă: 'a',
ą: 'a',
œ: 'a',
å: 'a',
ä: 'a',
á: 'a',
à: 'a',
â: 'a',
ã: 'a',
ç: 'c',
ć: 'c',
ĉ: 'c',
ċ: 'c',
č: 'c',
é: 'e',
è: 'e',
ê: 'e',
ë: 'e',
í: 'i',
ì: 'i',
î: 'i',
ï: 'i',
ú: 'u',
ñ: 'n',
ü: 'u',
ù: 'u',
û: 'u',
ß: 'es',
ď: 'd',
đ: 'd',
ē: 'e',
ĕ: 'e',
ė: 'e',
ę: 'e',
ě: 'e',
ĝ: 'g',
ğ: 'g',
ġ: 'g',
ģ: 'g',
ĥ: 'h',
ħ: 'h',
ĩ: 'i',
ī: 'i',
ĭ: 'i',
į: 'i',
ı: 'i',
ij: 'ij',
ĵ: 'j',
ķ: 'k',
ĺ: 'l',
ļ: 'l',
ľ: 'l',
ŀ: 'l',
ł: 'l',
ń: 'n',
ņ: 'n',
ň: 'n',
ʼn: 'n',
ō: 'o',
ŏ: 'o',
ő: 'o',
ŕ: 'r',
ŗ: 'r',
ř: 'r',
ś: 's',
ŝ: 's',
ş: 's',
š: 's',
ţ: 't',
ť: 't',
ŧ: 't',
ũ: 'u',
ū: 'u',
ŭ: 'u',
ů: 'u',
ű: 'u',
ų: 'u',
ŵ: 'w',
ŷ: 'y',
ź: 'z',
ż: 'z',
ž: 'z',
ſ: 's',
ƒ: 'f',
ơ: 'o',
ư: 'u',
ǎ: 'a',
ǐ: 'i',
ǒ: 'o',
ǔ: 'u',
ǖ: 'u',
ǘ: 'u',
ǚ: 'u',
ǜ: 'u',
ǻ: 'a',
ǽ: 'ae',
ǿ: 'oe'
};
constructor(private translate: TranslateService,
private textUtils: CoreTextUtilsProvider) { }
/** /**
* The metadataSettings field in libraryJson uses 1 for true and 0 for false. * The metadataSettings field in libraryJson uses 1 for true and 0 for false.
@ -43,6 +158,161 @@ export class CoreH5PUtilsProvider {
return JSON.stringify(metadataSettings); return JSON.stringify(metadataSettings);
} }
/**
* Determine the correct embed type to use.
*
* @param Embed type of the content.
* @param Embed type of the main library.
* @return Either 'div' or 'iframe'.
*/
determineEmbedType(contentEmbedType: string, libraryEmbedTypes: string): string {
// Detect content embed type.
let embedType = contentEmbedType.toLowerCase().indexOf('div') != -1 ? 'div' : 'iframe';
if (libraryEmbedTypes) {
// Check that embed type is available for library
const embedTypes = libraryEmbedTypes.toLowerCase();
if (embedTypes.indexOf(embedType) == -1) {
// Not available, pick default.
embedType = embedTypes.indexOf('div') != -1 ? 'div' : 'iframe';
}
}
return embedType;
}
/**
* Combines path with version.
*
* @param assets List of assets to get their URLs.
* @param assetsFolderPath The path of the folder where the assets are.
* @return List of urls.
*/
getAssetsUrls(assets: CoreH5PDependencyAsset[], assetsFolderPath: string = ''): string[] {
const urls = [];
assets.forEach((asset) => {
let url = asset.path;
// Add URL prefix if not external.
if (asset.path.indexOf('://') == -1 && assetsFolderPath) {
url = this.textUtils.concatenatePaths(assetsFolderPath, url);
}
// Add version if set.
if (asset.version) {
url += asset.version;
}
urls.push(url);
});
return urls;
}
/**
* Get the hash of a list of dependencies.
*
* @param dependencies Dependencies.
* @return Hash.
*/
getDependenciesHash(dependencies: {[machineName: string]: CoreH5PContentDependencyData}): string {
// Build hash of dependencies.
const toHash = [];
// Use unique identifier for each library version.
for (const name in dependencies) {
const dep = dependencies[name];
toHash.push(dep.machineName + '-' + dep.majorVersion + '.' + dep.minorVersion + '.' + dep.patchVersion);
}
// Sort in case the same dependencies comes in a different order.
toHash.sort((a, b) => {
return a.localeCompare(b);
});
// Calculate hash.
return <string> Md5.hashAsciiStr(toHash.join(''));
}
/**
* Provide localization for the Core JS.
*
* @return Object with the translations.
*/
getLocalization(): any {
return {
fullscreen: this.translate.instant('core.h5p.fullscreen'),
disableFullscreen: this.translate.instant('core.h5p.disablefullscreen'),
download: this.translate.instant('core.h5p.download'),
copyrights: this.translate.instant('core.h5p.copyright'),
embed: this.translate.instant('core.h5p.embed'),
size: this.translate.instant('core.h5p.size'),
showAdvanced: this.translate.instant('core.h5p.showadvanced'),
hideAdvanced: this.translate.instant('core.h5p.hideadvanced'),
advancedHelp: this.translate.instant('core.h5p.resizescript'),
copyrightInformation: this.translate.instant('core.h5p.copyright'),
close: this.translate.instant('core.h5p.close'),
title: this.translate.instant('core.h5p.title'),
author: this.translate.instant('core.h5p.author'),
year: this.translate.instant('core.h5p.year'),
source: this.translate.instant('core.h5p.source'),
license: this.translate.instant('core.h5p.license'),
thumbnail: this.translate.instant('core.h5p.thumbnail'),
noCopyrights: this.translate.instant('core.h5p.nocopyright'),
reuse: this.translate.instant('core.h5p.reuse'),
reuseContent: this.translate.instant('core.h5p.reuseContent'),
reuseDescription: this.translate.instant('core.h5p.reuseDescription'),
downloadDescription: this.translate.instant('core.h5p.downloadtitle'),
copyrightsDescription: this.translate.instant('core.h5p.copyrighttitle'),
embedDescription: this.translate.instant('core.h5p.embedtitle'),
h5pDescription: this.translate.instant('core.h5p.h5ptitle'),
contentChanged: this.translate.instant('core.h5p.contentchanged'),
startingOver: this.translate.instant('core.h5p.startingover'),
by: this.translate.instant('core.h5p.by'),
showMore: this.translate.instant('core.h5p.showmore'),
showLess: this.translate.instant('core.h5p.showless'),
subLevel: this.translate.instant('core.h5p.sublevel'),
confirmDialogHeader: this.translate.instant('core.h5p.confirmdialogheader'),
confirmDialogBody: this.translate.instant('core.h5p.confirmdialogbody'),
cancelLabel: this.translate.instant('core.h5p.cancellabel'),
confirmLabel: this.translate.instant('core.h5p.confirmlabel'),
licenseU: this.translate.instant('core.h5p.undisclosed'),
licenseCCBY: this.translate.instant('core.h5p.ccattribution'),
licenseCCBYSA: this.translate.instant('core.h5p.ccattributionsa'),
licenseCCBYND: this.translate.instant('core.h5p.ccattributionnd'),
licenseCCBYNC: this.translate.instant('core.h5p.ccattributionnc'),
licenseCCBYNCSA: this.translate.instant('core.h5p.ccattributionncsa'),
licenseCCBYNCND: this.translate.instant('core.h5p.ccattributionncnd'),
licenseCC40: this.translate.instant('core.h5p.licenseCC40'),
licenseCC30: this.translate.instant('core.h5p.licenseCC30'),
licenseCC25: this.translate.instant('core.h5p.licenseCC25'),
licenseCC20: this.translate.instant('core.h5p.licenseCC20'),
licenseCC10: this.translate.instant('core.h5p.licenseCC10'),
licenseGPL: this.translate.instant('core.h5p.licenseGPL'),
licenseV3: this.translate.instant('core.h5p.licenseV3'),
licenseV2: this.translate.instant('core.h5p.licenseV2'),
licenseV1: this.translate.instant('core.h5p.licenseV1'),
licensePD: this.translate.instant('core.h5p.pd'),
licenseCC010: this.translate.instant('core.h5p.licenseCC010'),
licensePDM: this.translate.instant('core.h5p.pdm'),
licenseC: this.translate.instant('core.h5p.copyrightstring'),
contentType: this.translate.instant('core.h5p.contenttype'),
licenseExtras: this.translate.instant('core.h5p.licenseextras'),
changes: this.translate.instant('core.h5p.changelog'),
contentCopied: this.translate.instant('core.h5p.contentCopied'),
connectionLost: this.translate.instant('core.h5p.connectionLost'),
connectionReestablished: this.translate.instant('core.h5p.connectionReestablished'),
resubmitScores: this.translate.instant('core.h5p.resubmitScores'),
offlineDialogHeader: this.translate.instant('core.h5p.offlineDialogHeader'),
offlineDialogBody: this.translate.instant('core.h5p.offlineDialogBody'),
offlineDialogRetryMessage: this.translate.instant('core.h5p.offlineDialogRetryMessage'),
offlineDialogRetryButtonLabel: this.translate.instant('core.h5p.offlineDialogRetryButtonLabel'),
offlineSuccessfulSubmit: this.translate.instant('core.h5p.offlineSuccessfulSubmit'),
};
}
/** /**
* Convert list of library parameter values to csv. * Convert list of library parameter values to csv.
* *
@ -68,4 +338,71 @@ export class CoreH5PUtilsProvider {
return ''; return '';
} }
/**
* Convert strings of text into simple kebab case slugs. Based on H5PCore::slugify.
*
* @param input The string to slugify.
* @return Slugified text.
*/
slugify(input: string): string {
input = input || '';
input = input.toLowerCase();
// Replace common chars.
let newInput = '';
for (let i = 0; i < input.length; i++) {
const char = input[i];
newInput += this.SLUGIFY_MAP[char] || char;
}
// Replace everything else.
newInput = newInput.replace(/[^a-z0-9]/g, '-');
// Prevent double hyphen
newInput = newInput.replace(/-{2,}/g, '-');
// Prevent hyphen in beginning or end.
newInput = newInput.replace(/(^-+|-+$)/g, '');
// Prevent too long slug.
if (newInput.length > 91) {
newInput = newInput.substr(0, 92);
}
// Prevent empty slug
if (newInput === '') {
newInput = 'interactive';
}
return newInput;
}
/**
* Determine if params contain any match.
*
* @param params Parameters.
* @param pattern Regular expression to identify pattern.
* @return True if params matches pattern.
*/
textAddonMatches(params: any, pattern: string): boolean {
if (typeof params == 'string') {
if (params.match(pattern)) {
return true;
}
} else if (typeof params == 'object') {
for (const key in params) {
const value = params[key];
if (this.textAddonMatches(value, pattern)) {
return true;
}
}
}
return false;
}
} }

View File

@ -1239,4 +1239,19 @@ export class CoreFileProvider {
isFileInAppFolder(path: string): boolean { isFileInAppFolder(path: string): boolean {
return path.indexOf(this.basePath) != -1; return path.indexOf(this.basePath) != -1;
} }
/**
* Get the full path to the www folder at runtime.
*
* @return Path.
*/
getWWWPath(): string {
const position = window.location.href.indexOf('index.html');
if (position != -1) {
return window.location.href.substr(0, position);
}
return window.location.href;
}
} }