MOBILE-2235 h5p: Display download button in H5P placeholder

main
Dani Palou 2019-10-31 12:30:45 +01:00
parent 690544afbf
commit 9b637fc496
10 changed files with 300 additions and 19 deletions

View File

@ -1,4 +1,4 @@
<div [class.core-loading-container]="loading"> <div [class.core-loading-container]="loading" [ngStyle]="{'width': iframeWidth, 'height': iframeHeight}">
<iframe #iframe [hidden]="loading" class="core-iframe" [ngStyle]="{'width': iframeWidth, 'height': iframeHeight}" [src]="safeUrl"></iframe> <iframe #iframe [hidden]="loading" class="core-iframe" [ngStyle]="{'width': iframeWidth, 'height': iframeHeight}" [src]="safeUrl"></iframe>
<span class="core-loading-spinner"> <span class="core-loading-spinner">
<ion-spinner *ngIf="loading"></ion-spinner> <ion-spinner *ngIf="loading"></ion-spinner>

View File

@ -1,7 +1,4 @@
ion-app.app-root core-iframe { ion-app.app-root core-iframe {
> div {
height: 100%;
}
iframe { iframe {
border: 0; border: 0;
display: block; display: block;

View File

@ -30,6 +30,7 @@ import { CORE_COURSES_PROVIDERS } from '@core/courses/courses.module';
import { CORE_FILEUPLOADER_PROVIDERS } from '@core/fileuploader/fileuploader.module'; import { CORE_FILEUPLOADER_PROVIDERS } from '@core/fileuploader/fileuploader.module';
import { CORE_FILTER_PROVIDERS } from '@core/filter/filter.module'; import { CORE_FILTER_PROVIDERS } from '@core/filter/filter.module';
import { CORE_GRADES_PROVIDERS } from '@core/grades/grades.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_LOGIN_PROVIDERS } from '@core/login/login.module';
import { CORE_MAINMENU_PROVIDERS } from '@core/mainmenu/mainmenu.module'; import { CORE_MAINMENU_PROVIDERS } from '@core/mainmenu/mainmenu.module';
import { CORE_QUESTION_PROVIDERS } from '@core/question/question.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_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(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_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. // We cannot inject anything to this constructor. Use the Injector to inject all the providers into the instance.
for (const i in providers) { for (const i in providers) {

View File

@ -402,7 +402,7 @@ export type CoreFilterFilter = {
*/ */
export type CoreFilterGetAvailableInContextResult = { export type CoreFilterGetAvailableInContextResult = {
filters: CoreFilterFilter[]; // Available filters. filters: CoreFilterFilter[]; // Available filters.
warning: CoreWSExternalWarning[]; // List of warnings. warnings: CoreWSExternalWarning[]; // List of warnings.
}; };
/** /**

View File

@ -1,9 +1,14 @@
<div *ngIf="!showPackage" class="core-h5p-placeholder"> <div *ngIf="!showPackage" class="core-h5p-placeholder">
<button *ngIf="!loading" class="core-h5p-placeholder-play-button" ion-button icon-only clear color="dark" (click)="play($event)"> <button *ngIf="!loading && !errorMessage" class="core-h5p-placeholder-play-button" ion-button icon-only clear (click)="play($event)">
<core-icon name="fa-play-circle"></core-icon> <core-icon name="fa-play-circle"></core-icon>
</button> </button>
<ion-spinner *ngIf="loading" class="core-h5p-placeholder-spinner"></ion-spinner> <ion-spinner *ngIf="loading && !errorMessage" class="core-h5p-placeholder-spinner"></ion-spinner>
<div *ngIf="errorMessage" class="core-h5p-placeholder-error">{{ errorMessage }}</div>
<div class="core-h5p-placeholder-download-container">
<core-download-refresh [status]="state" [enabled]="canDownload" [loading]="calculating" [canTrustDownload]="true" (action)="download()"></core-download-refresh>
</div>
</div> </div>
<core-iframe *ngIf="showPackage" [src]="src"></core-iframe> <core-iframe *ngIf="showPackage" [src]="playerSrc" iframeHeight="230px"></core-iframe>

View File

@ -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 { ion-app.app-root core-h5p-player {
.core-h5p-placeholder { .core-h5p-placeholder {
position: relative; position: relative;
width: 100%; width: 100%;
height: 230px; height: 230px;
background: url('../assets/img/icons/h5p.svg') center top 25px / 100px auto no-repeat $core-h5p-placeholder-bg-color; 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; position: absolute;
top: 50%; top: 50%;
left: 50%; left: 50%;
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
}
.core-h5p-placeholder-play-button {
font-size: 30px; font-size: 30px;
min-height: 50px; min-height: 50px;
} }
.core-h5p-placeholder-download-container { .core-h5p-placeholder-download-container {
position: absolute; position: absolute;
right: 10px; top: 0;
font-size: 1.8em; 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;
} }
} }

View File

@ -12,8 +12,12 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // 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 { 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. * Component to render an H5P package.
@ -22,17 +26,39 @@ import { CoreSitesProvider } from '@providers/sites';
selector: 'core-h5p-player', selector: 'core-h5p-player',
templateUrl: 'core-h5p-player.html' 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. @Input() src: string; // The URL of the player to display the H5P package.
playerSrc: string;
showPackage = false; showPackage = false;
loading = false; loading = false;
status: string; status: string;
canDownload: boolean; canDownload: boolean;
calculating = true; calculating = true;
errorMessage: string;
constructor(public elementRef: ElementRef, 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; 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. // Get auto-login URL so the user is automatically authenticated.
this.sitesProvider.getCurrentSite().getAutoLoginUrl(this.src, false).then((url) => { this.sitesProvider.getCurrentSite().getAutoLoginUrl(this.src, false).then((url) => {
this.src = url; this.playerSrc = url;
this.loading = false; this.loading = false;
this.showPackage = true; 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;
}
} }

View File

@ -14,6 +14,12 @@
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { CoreH5PComponentsModule } from './components/components.module'; import { CoreH5PComponentsModule } from './components/components.module';
import { CoreH5PProvider } from './providers/h5p';
// List of providers (without handlers).
export const CORE_H5P_PROVIDERS: any[] = [
CoreH5PProvider
];
@NgModule({ @NgModule({
declarations: [], declarations: [],
@ -21,6 +27,7 @@ import { CoreH5PComponentsModule } from './components/components.module';
CoreH5PComponentsModule CoreH5PComponentsModule
], ],
providers: [ providers: [
CoreH5PProvider
], ],
exports: [] exports: []
}) })

View File

@ -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<boolean> {
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<CoreWSExternalFile> {
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<any> {
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<any> {
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.
};

View File

@ -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-selected-shadow: 2px 2px 4px $gray-dark !default;
$core-dd-question-colors: $white, $blue-light, #DCDCDC, #D8BFD8, #87CEFA, #DAA520, #FFD700, #F0E68C !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 // Mixins
// ------------------------- // -------------------------
@mixin core-transition($where: all, $time: 500ms) { @mixin core-transition($where: all, $time: 500ms) {