diff --git a/src/components/iframe/core-iframe.html b/src/components/iframe/core-iframe.html
index a94b5d110..7bdeac425 100644
--- a/src/components/iframe/core-iframe.html
+++ b/src/components/iframe/core-iframe.html
@@ -1,4 +1,4 @@
-
+
diff --git a/src/components/iframe/iframe.scss b/src/components/iframe/iframe.scss
index 0d0e5ca92..782a554cb 100644
--- a/src/components/iframe/iframe.scss
+++ b/src/components/iframe/iframe.scss
@@ -1,7 +1,4 @@
ion-app.app-root core-iframe {
- > div {
- height: 100%;
- }
iframe {
border: 0;
display: block;
diff --git a/src/core/compile/providers/compile.ts b/src/core/compile/providers/compile.ts
index bbfc35f48..8f0c7e1b6 100644
--- a/src/core/compile/providers/compile.ts
+++ b/src/core/compile/providers/compile.ts
@@ -30,6 +30,7 @@ import { CORE_COURSES_PROVIDERS } from '@core/courses/courses.module';
import { CORE_FILEUPLOADER_PROVIDERS } from '@core/fileuploader/fileuploader.module';
import { CORE_FILTER_PROVIDERS } from '@core/filter/filter.module';
import { CORE_GRADES_PROVIDERS } from '@core/grades/grades.module';
+import { CORE_H5P_PROVIDERS } from '@core/h5p/h5p.module';
import { CORE_LOGIN_PROVIDERS } from '@core/login/login.module';
import { CORE_MAINMENU_PROVIDERS } from '@core/mainmenu/mainmenu.module';
import { CORE_QUESTION_PROVIDERS } from '@core/question/question.module';
@@ -236,7 +237,7 @@ export class CoreCompileProvider {
.concat(ADDON_MOD_SURVEY_PROVIDERS).concat(ADDON_MOD_URL_PROVIDERS).concat(ADDON_MOD_WIKI_PROVIDERS)
.concat(ADDON_MOD_WORKSHOP_PROVIDERS).concat(ADDON_NOTES_PROVIDERS).concat(ADDON_NOTIFICATIONS_PROVIDERS)
.concat(CORE_PUSHNOTIFICATIONS_PROVIDERS).concat(ADDON_REMOTETHEMES_PROVIDERS).concat(CORE_BLOCK_PROVIDERS)
- .concat(CORE_FILTER_PROVIDERS);
+ .concat(CORE_FILTER_PROVIDERS).concat(CORE_H5P_PROVIDERS);
// We cannot inject anything to this constructor. Use the Injector to inject all the providers into the instance.
for (const i in providers) {
diff --git a/src/core/filter/providers/filter.ts b/src/core/filter/providers/filter.ts
index 42802335f..a404d5616 100644
--- a/src/core/filter/providers/filter.ts
+++ b/src/core/filter/providers/filter.ts
@@ -402,7 +402,7 @@ export type CoreFilterFilter = {
*/
export type CoreFilterGetAvailableInContextResult = {
filters: CoreFilterFilter[]; // Available filters.
- warning: CoreWSExternalWarning[]; // List of warnings.
+ warnings: CoreWSExternalWarning[]; // List of warnings.
};
/**
diff --git a/src/core/h5p/components/h5p-player/core-h5p-player.html b/src/core/h5p/components/h5p-player/core-h5p-player.html
index 3d9e67912..af48001df 100644
--- a/src/core/h5p/components/h5p-player/core-h5p-player.html
+++ b/src/core/h5p/components/h5p-player/core-h5p-player.html
@@ -1,9 +1,14 @@
-
-
+
diff --git a/src/core/h5p/components/h5p-player/h5p-player.scss b/src/core/h5p/components/h5p-player/h5p-player.scss
index 0c963aa79..b5ba7f82c 100644
--- a/src/core/h5p/components/h5p-player/h5p-player.scss
+++ b/src/core/h5p/components/h5p-player/h5p-player.scss
@@ -1,23 +1,56 @@
+// H5P variables.
+$core-h5p-placeholder-bg-color: $gray !default;
+$core-h5p-placeholder-text-color: $text-color !default;
+
ion-app.app-root core-h5p-player {
.core-h5p-placeholder {
position: relative;
width: 100%;
height: 230px;
background: url('../assets/img/icons/h5p.svg') center top 25px / 100px auto no-repeat $core-h5p-placeholder-bg-color;
+ color: $core-h5p-placeholder-text-color;
- .core-h5p-placeholder-play-button {
+ .icon {
+ color: $core-h5p-placeholder-text-color;
+ }
+
+ .core-h5p-placeholder-play-button, .core-h5p-placeholder-spinner {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
+ }
+
+ .core-h5p-placeholder-play-button {
font-size: 30px;
min-height: 50px;
}
.core-h5p-placeholder-download-container {
position: absolute;
- right: 10px;
- font-size: 1.8em;
+ top: 0;
+ right: 0;
+
+ ion-spinner {
+ margin-right: 0.75em;
+ }
+
+ core-download-refresh > ion-icon {
+ margin: 0.4rem 0.2rem;
+ padding: 0 0.5em;
+ line-height: .67;
+ }
+ }
+
+ .core-h5p-placeholder-error {
+ position: absolute;
+ width: 100%;
+ text-align: center;
+ top: 50%;
+ }
+
+ ion-spinner circle {
+ stroke: $core-h5p-placeholder-text-color;
}
}
diff --git a/src/core/h5p/components/h5p-player/h5p-player.ts b/src/core/h5p/components/h5p-player/h5p-player.ts
index e6a73b2a4..0c4f3e937 100644
--- a/src/core/h5p/components/h5p-player/h5p-player.ts
+++ b/src/core/h5p/components/h5p-player/h5p-player.ts
@@ -12,8 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import { Component, Input, ElementRef } from '@angular/core';
+import { Component, Input, ElementRef, OnInit, SimpleChange } from '@angular/core';
import { CoreSitesProvider } from '@providers/sites';
+import { CoreTextUtilsProvider } from '@providers/utils/text';
+import { CoreUrlUtilsProvider } from '@providers/utils/url';
+import { CoreUtilsProvider } from '@providers/utils/utils';
+import { CoreH5PProvider } from '@core/h5p/providers/h5p';
/**
* Component to render an H5P package.
@@ -22,17 +26,39 @@ import { CoreSitesProvider } from '@providers/sites';
selector: 'core-h5p-player',
templateUrl: 'core-h5p-player.html'
})
-export class CoreH5PPlayerComponent {
+export class CoreH5PPlayerComponent implements OnInit {
@Input() src: string; // The URL of the player to display the H5P package.
+ playerSrc: string;
showPackage = false;
loading = false;
status: string;
canDownload: boolean;
calculating = true;
+ errorMessage: string;
constructor(public elementRef: ElementRef,
- protected sitesProvider: CoreSitesProvider) {
+ protected sitesProvider: CoreSitesProvider,
+ protected urlUtils: CoreUrlUtilsProvider,
+ protected utils: CoreUtilsProvider,
+ protected textUtils: CoreTextUtilsProvider,
+ protected h5pProvider: CoreH5PProvider) { }
+
+ /**
+ * Component being initialized.
+ */
+ ngOnInit(): void {
+ this.checkCanDownload();
+ }
+
+ /**
+ * Detect changes on input properties.
+ */
+ ngOnChanges(changes: {[name: string]: SimpleChange}): void {
+ // If it's already playing and the src changes, don't change the player src, the user could lose data.
+ if (changes.src && !this.showPackage) {
+ this.checkCanDownload();
+ }
}
/**
@@ -46,11 +72,57 @@ export class CoreH5PPlayerComponent {
this.loading = true;
+ // @TODO: Check if package is downloaded and use the local player if so.
+
// Get auto-login URL so the user is automatically authenticated.
this.sitesProvider.getCurrentSite().getAutoLoginUrl(this.src, false).then((url) => {
- this.src = url;
+ this.playerSrc = url;
this.loading = false;
this.showPackage = true;
});
}
+
+ /**
+ * Download the package.
+ */
+ download(): void {
+ // @TODO: Implement package download.
+ }
+
+ /**
+ * Check if the package can be downloaded.
+ */
+ protected checkCanDownload(): void {
+ if (this.src && this.h5pProvider.canGetTrustedH5PFileInSite()) {
+ const params = this.urlUtils.extractUrlParams(this.src);
+
+ // @todo: Check if H5P offline is disabled in the site.
+
+ // Now check if the package can be played.
+ this.calculating = true;
+
+ const options = {
+ frame: this.utils.isTrueOrOne(params.frame),
+ export: this.utils.isTrueOrOne(params.export),
+ embed: this.utils.isTrueOrOne(params.embed),
+ copyright: this.utils.isTrueOrOne(params.copyright),
+ };
+
+ this.h5pProvider.getTrustedH5PFile(params.url, options).then((file) => {
+ this.canDownload = true;
+ this.errorMessage = undefined;
+ }).catch((error) => {
+ this.canDownload = false;
+ this.errorMessage = this.textUtils.getErrorMessageFromError(error);
+ }).finally(() => {
+ this.calculating = false;
+ });
+
+ return;
+ }
+
+ this.calculating = false;
+ this.canDownload = false;
+ this.errorMessage = undefined;
+ }
}
diff --git a/src/core/h5p/h5p.module.ts b/src/core/h5p/h5p.module.ts
index 4fb4f6ba4..ddc625a0b 100644
--- a/src/core/h5p/h5p.module.ts
+++ b/src/core/h5p/h5p.module.ts
@@ -14,6 +14,12 @@
import { NgModule } from '@angular/core';
import { CoreH5PComponentsModule } from './components/components.module';
+import { CoreH5PProvider } from './providers/h5p';
+
+// List of providers (without handlers).
+export const CORE_H5P_PROVIDERS: any[] = [
+ CoreH5PProvider
+];
@NgModule({
declarations: [],
@@ -21,6 +27,7 @@ import { CoreH5PComponentsModule } from './components/components.module';
CoreH5PComponentsModule
],
providers: [
+ CoreH5PProvider
],
exports: []
})
diff --git a/src/core/h5p/providers/h5p.ts b/src/core/h5p/providers/h5p.ts
new file mode 100644
index 000000000..335e5025f
--- /dev/null
+++ b/src/core/h5p/providers/h5p.ts
@@ -0,0 +1,169 @@
+// (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 { CoreLoggerProvider } from '@providers/logger';
+import { CoreSitesProvider } from '@providers/sites';
+import { CoreSite, CoreSiteWSPreSets } from '@classes/site';
+import { CoreWSExternalWarning, CoreWSExternalFile } from '@providers/ws';
+
+/**
+ * Service to provide H5P functionalities.
+ */
+@Injectable()
+export class CoreH5PProvider {
+
+ protected ROOT_CACHE_KEY = 'mmH5P:';
+
+ protected logger;
+
+ constructor(logger: CoreLoggerProvider,
+ private sitesProvider: CoreSitesProvider) {
+
+ this.logger = logger.getInstance('CoreFilterProvider');
+ }
+
+ /**
+ * Returns whether or not WS to get trusted H5P file is available.
+ *
+ * @param siteId Site ID. If not defined, current site.
+ * @return Promise resolved with true if ws is available, false otherwise.
+ * @since 3.8
+ */
+ canGetTrustedH5PFile(siteId?: string): Promise {
+ return this.sitesProvider.getSite(siteId).then((site) => {
+ return this.canGetTrustedH5PFileInSite(site);
+ });
+ }
+
+ /**
+ * Returns whether or not WS to get trusted H5P file is available in a certain site.
+ *
+ * @param site Site. If not defined, current site.
+ * @return Promise resolved with true if ws is available, false otherwise.
+ * @since 3.4
+ */
+ canGetTrustedH5PFileInSite(site?: CoreSite): boolean {
+ site = site || this.sitesProvider.getCurrentSite();
+
+ return site.wsAvailable('core_h5p_get_trusted_h5p_file');
+ }
+
+ /**
+ * Get a trusted H5P file.
+ *
+ * @param url The file URL.
+ * @param options Options.
+ * @param ignoreCache Whether to ignore cache..
+ * @param siteId Site ID. If not defined, current site.
+ * @return Promise resolved with the file data.
+ */
+ getTrustedH5PFile(url: string, options: CoreH5PGetTrustedFileOptions, ignoreCache?: boolean, siteId?: string)
+ : Promise {
+
+ return this.sitesProvider.getSite(siteId).then((site) => {
+
+ const data = {
+ url: url,
+ frame: options.frame ? 1 : 0,
+ export: options.export ? 1 : 0,
+ embed: options.embed ? 1 : 0,
+ copyright: options.copyright ? 1 : 0,
+ },
+ preSets: CoreSiteWSPreSets = {
+ cacheKey: this.getTrustedH5PFileCacheKey(url),
+ updateFrequency: CoreSite.FREQUENCY_RARELY
+ };
+
+ if (ignoreCache) {
+ preSets.getFromCache = false;
+ preSets.emergencyCache = false;
+ }
+
+ return site.read('core_h5p_get_trusted_h5p_file', data, preSets).then((result: CoreH5PGetTrustedH5PFileResult): any => {
+ if (result.warnings && result.warnings.length) {
+ return Promise.reject(result.warnings[0]);
+ }
+
+ if (result.files && result.files.length) {
+ return result.files[0];
+ }
+
+ return Promise.reject(null);
+ });
+ });
+ }
+
+ /**
+ * Get cache key for trusted H5P file WS calls.
+ *
+ * @param url The file URL.
+ * @return Cache key.
+ */
+ protected getTrustedH5PFileCacheKey(url: string): string {
+ return this.getTrustedH5PFilePrefixCacheKey() + url;
+ }
+
+ /**
+ * Get prefixed cache key for trusted H5P file WS calls.
+ *
+ * @return Cache key.
+ */
+ protected getTrustedH5PFilePrefixCacheKey(): string {
+ return this.ROOT_CACHE_KEY + 'trustedH5PFile:';
+ }
+
+ /**
+ * Invalidates all trusted H5P file WS calls.
+ *
+ * @param siteId Site ID (empty for current site).
+ * @return Promise resolved when the data is invalidated.
+ */
+ invalidateAllGetTrustedH5PFile(siteId?: string): Promise {
+ return this.sitesProvider.getSite(siteId).then((site) => {
+ return site.invalidateWsCacheForKeyStartingWith(this.getTrustedH5PFilePrefixCacheKey());
+ });
+ }
+
+ /**
+ * Invalidates get trusted H5P file WS call.
+ *
+ * @param url The URL of the file.
+ * @param siteId Site ID (empty for current site).
+ * @return Promise resolved when the data is invalidated.
+ */
+ invalidateAvailableInContexts(url: string, siteId?: string): Promise {
+ return this.sitesProvider.getSite(siteId).then((site) => {
+ return site.invalidateWsCacheForKey(this.getTrustedH5PFileCacheKey(url));
+ });
+ }
+}
+
+/**
+ * Options for core_h5p_get_trusted_h5p_file.
+ */
+export type CoreH5PGetTrustedFileOptions = {
+ frame?: boolean; // Whether to show the bar options below the content.
+ export?: boolean; // Whether to allow to download the package.
+ embed?: boolean; // Whether to allow to copy the code to your site.
+ copyright?: boolean; // The copyright option.
+};
+
+/**
+ * Result of core_h5p_get_trusted_h5p_file.
+ */
+export type CoreH5PGetTrustedH5PFileResult = {
+ files: CoreWSExternalFile[]; // Files.
+ warnings: CoreWSExternalWarning[]; // List of warnings.
+};
diff --git a/src/theme/variables.scss b/src/theme/variables.scss
index 326cc4671..ebab93935 100644
--- a/src/theme/variables.scss
+++ b/src/theme/variables.scss
@@ -369,9 +369,6 @@ $core-question-state-incorrect-color: $red-light !default;
$core-dd-question-selected-shadow: 2px 2px 4px $gray-dark !default;
$core-dd-question-colors: $white, $blue-light, #DCDCDC, #D8BFD8, #87CEFA, #DAA520, #FFD700, #F0E68C !default;
-// H5P variables.
-$core-h5p-placeholder-bg-color: $gray-dark !default;
-
// Mixins
// -------------------------
@mixin core-transition($where: all, $time: 500ms) {