MOBILE-3666 h5p: Implement H5P components
parent
0ead40c72e
commit
7956d8e563
|
@ -17,7 +17,7 @@ import { Injectable, ViewContainerRef, ComponentFactoryResolver } from '@angular
|
|||
import { CoreFilterDefaultHandler } from '@features/filter/services/handlers/default-filter';
|
||||
import { CoreFilterFilter, CoreFilterFormatTextOptions } from '@features/filter/services/filter';
|
||||
import { makeSingleton } from '@singletons';
|
||||
// @todo import { CoreH5PPlayerComponent } from '@core/h5p/components/h5p-player/h5p-player';
|
||||
import { CoreH5PPlayerComponent } from '@features/h5p/components/h5p-player/h5p-player';
|
||||
|
||||
/**
|
||||
* Handler to support the Display H5P filter.
|
||||
|
@ -80,32 +80,31 @@ export class AddonFilterDisplayH5PHandlerService extends CoreFilterDefaultHandle
|
|||
* @return If async, promise resolved when done.
|
||||
*/
|
||||
handleHtml(
|
||||
container: HTMLElement, // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||
filter: CoreFilterFilter, // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||
options: CoreFilterFormatTextOptions, // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||
viewContainerRef: ViewContainerRef, // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||
component?: string, // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||
componentId?: string | number, // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||
container: HTMLElement,
|
||||
filter: CoreFilterFilter,
|
||||
options: CoreFilterFormatTextOptions,
|
||||
viewContainerRef: ViewContainerRef,
|
||||
component?: string,
|
||||
componentId?: string | number,
|
||||
siteId?: string, // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||
): void | Promise<void> {
|
||||
// @todo
|
||||
|
||||
// const placeholders = <HTMLElement[]> Array.from(container.querySelectorAll('div.core-h5p-tmp-placeholder'));
|
||||
const placeholders = <HTMLElement[]> Array.from(container.querySelectorAll('div.core-h5p-tmp-placeholder'));
|
||||
|
||||
// placeholders.forEach((placeholder) => {
|
||||
// const url = placeholder.getAttribute('data-player-src');
|
||||
placeholders.forEach((placeholder) => {
|
||||
const url = placeholder.getAttribute('data-player-src') || '';
|
||||
|
||||
// Create the component to display the player.
|
||||
// const factory = this.factoryResolver.resolveComponentFactory(CoreH5PPlayerComponent);
|
||||
// const componentRef = viewContainerRef.createComponent<CoreH5PPlayerComponent>(factory);
|
||||
// Create the component to display the player.
|
||||
const factory = this.factoryResolver.resolveComponentFactory(CoreH5PPlayerComponent);
|
||||
const componentRef = viewContainerRef.createComponent<CoreH5PPlayerComponent>(factory);
|
||||
|
||||
// componentRef.instance.src = url;
|
||||
// componentRef.instance.component = component;
|
||||
// componentRef.instance.componentId = componentId;
|
||||
componentRef.instance.src = url;
|
||||
componentRef.instance.component = component;
|
||||
componentRef.instance.componentId = componentId;
|
||||
|
||||
// // Move the component to its right position.
|
||||
// placeholder.parentElement?.replaceChild(componentRef.instance.elementRef.nativeElement, placeholder);
|
||||
// });
|
||||
// Move the component to its right position.
|
||||
placeholder.parentElement?.replaceChild(componentRef.instance.elementRef.nativeElement, placeholder);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 23.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 345 150" style="enable-background:new 0 0 345 150;" xml:space="preserve">
|
||||
<g>
|
||||
<path d="M325.7,14.7C317.6,6.9,305.3,3,289,3h-43.5H234v31h-66l-5.4,22.2c4.5-2.1,10.9-4.2,15.3-5.3c4.4-1.1,8.8-0.9,13.1-0.9
|
||||
c14.6,0,26.5,4.5,35.6,13.3c9.1,8.8,13.6,20,13.6,33.4c0,9.4-2.3,18.5-7,27.2s-11.3,15.4-19.9,20c-3.1,1.6-6.5,3.1-10.2,4.1h42.4
|
||||
H259V95h25c18.2,0,31.7-4.2,40.6-12.5s13.3-19.9,13.3-34.6C337.9,33.6,333.8,22.5,325.7,14.7z M288.7,60.6c-3.5,3-9.6,4.4-18.3,4.4
|
||||
H259V33h13.2c8.4,0,14.2,1.5,17.2,4.7c3.1,3.2,4.6,6.9,4.6,11.5C294,53.9,292.2,57.6,288.7,60.6z"/>
|
||||
<path d="M176.5,76.3c-7.9,0-14.7,4.6-18,11.2L119,81.9L136.8,3h-23.6H101v62H51V3H7v145h44V95h50v53h12.2h42
|
||||
c-6.7-2-12.5-4.6-17.2-8.1c-4.8-3.6-8.7-7.7-11.7-12.3c-3-4.6-5.3-9.7-7.3-16.5l39.6-5.7c3.3,6.6,10.1,11.1,17.9,11.1
|
||||
c11.1,0,20.1-9,20.1-20.1S187.5,76.3,176.5,76.3z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
|
@ -0,0 +1,44 @@
|
|||
// (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 { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { IonicModule } from '@ionic/angular';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
|
||||
import { CoreDirectivesModule } from '@directives/directives.module';
|
||||
import { CoreComponentsModule } from '@components/components.module';
|
||||
import { CoreH5PPlayerComponent } from './h5p-player/h5p-player';
|
||||
import { CoreH5PIframeComponent } from './h5p-iframe/h5p-iframe';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
CoreH5PPlayerComponent,
|
||||
CoreH5PIframeComponent,
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
IonicModule,
|
||||
CoreDirectivesModule,
|
||||
TranslateModule.forChild(),
|
||||
CoreComponentsModule,
|
||||
],
|
||||
providers: [
|
||||
],
|
||||
exports: [
|
||||
CoreH5PPlayerComponent,
|
||||
CoreH5PIframeComponent,
|
||||
],
|
||||
})
|
||||
export class CoreH5PComponentsModule {}
|
|
@ -0,0 +1,5 @@
|
|||
<core-loading [hideUntil]="iframeSrc" class="core-loading-center safe-area-page">
|
||||
<core-iframe *ngIf="iframeSrc" [src]="iframeSrc" iframeHeight="auto" [allowFullscreen]="true" (loaded)="iframeLoaded()">
|
||||
</core-iframe>
|
||||
<script *ngIf="resizeScript && iframeSrc" type="text/javascript" [src]="resizeScript"></script>
|
||||
</core-loading>
|
|
@ -0,0 +1,223 @@
|
|||
// (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 { Component, Input, Output, ElementRef, OnChanges, SimpleChange, EventEmitter, OnDestroy } from '@angular/core';
|
||||
import { NavigationEnd, Router } from '@angular/router';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { filter } from 'rxjs/operators';
|
||||
|
||||
import { CoreFile } from '@services/file';
|
||||
import { CoreFilepool } from '@services/filepool';
|
||||
import { CoreFileHelper } from '@services/file-helper';
|
||||
import { CoreSites } from '@services/sites';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { CoreUrlUtils } from '@services/utils/url';
|
||||
import { CoreH5P } from '@features/h5p/services/h5p';
|
||||
import { CoreConstants } from '@/core/constants';
|
||||
import { CoreSite } from '@classes/site';
|
||||
import { CoreLogger } from '@singletons/logger';
|
||||
import { CoreH5PCore, CoreH5PDisplayOptions } from '../../classes/core';
|
||||
import { CoreH5PHelper } from '../../classes/helper';
|
||||
|
||||
/**
|
||||
* Component to render an iframe with an H5P package.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'core-h5p-iframe',
|
||||
templateUrl: 'core-h5p-iframe.html',
|
||||
})
|
||||
export class CoreH5PIframeComponent implements OnChanges, OnDestroy {
|
||||
|
||||
@Input() fileUrl?: string; // The URL of the H5P file. If not supplied, onlinePlayerUrl is required.
|
||||
@Input() displayOptions?: CoreH5PDisplayOptions; // Display options.
|
||||
@Input() onlinePlayerUrl?: string; // The URL of the online player to display the H5P package.
|
||||
@Input() trackComponent?: string; // Component to send xAPI events to.
|
||||
@Input() contextId?: number; // Context ID. Required for tracking.
|
||||
@Output() onIframeUrlSet = new EventEmitter<{src: string; online: boolean}>();
|
||||
@Output() onIframeLoaded = new EventEmitter<void>();
|
||||
|
||||
iframeSrc?: string;
|
||||
|
||||
protected site: CoreSite;
|
||||
protected siteId: string;
|
||||
protected siteCanDownload: boolean;
|
||||
protected logger: CoreLogger;
|
||||
protected currentPageRoute?: string;
|
||||
protected subscription: Subscription;
|
||||
protected iframeLoadedOnce = false;
|
||||
|
||||
constructor(
|
||||
public elementRef: ElementRef,
|
||||
router: Router,
|
||||
) {
|
||||
|
||||
this.logger = CoreLogger.getInstance('CoreH5PIframeComponent');
|
||||
this.site = CoreSites.instance.getCurrentSite()!;
|
||||
this.siteId = this.site.getId();
|
||||
this.siteCanDownload = this.site.canDownloadFiles() && !CoreH5P.instance.isOfflineDisabledInSite();
|
||||
|
||||
// Send resize events when the page holding this component is re-entered.
|
||||
// @todo: Check that this works as expected.
|
||||
this.currentPageRoute = router.url;
|
||||
this.subscription = router.events
|
||||
.pipe(filter(event => event instanceof NavigationEnd))
|
||||
.subscribe((event: NavigationEnd) => {
|
||||
if (!this.iframeLoadedOnce || event.urlAfterRedirects == this.currentPageRoute) {
|
||||
return;
|
||||
}
|
||||
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect changes on input properties.
|
||||
*/
|
||||
ngOnChanges(changes: {[name: string]: SimpleChange}): void {
|
||||
// If it's already playing don't change it.
|
||||
if ((changes.fileUrl || changes.onlinePlayerUrl) && !this.iframeSrc) {
|
||||
this.play();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Play the H5P.
|
||||
*
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
protected async play(): Promise<void> {
|
||||
let localUrl: string | undefined;
|
||||
let state: string;
|
||||
|
||||
if (this.fileUrl) {
|
||||
state = await CoreFilepool.instance.getFileStateByUrl(this.siteId, this.fileUrl);
|
||||
} else {
|
||||
state = CoreConstants.NOT_DOWNLOADABLE;
|
||||
}
|
||||
|
||||
if (this.siteCanDownload && CoreFileHelper.instance.isStateDownloaded(state)) {
|
||||
// Package is downloaded, use the local URL.
|
||||
localUrl = await this.getLocalUrl();
|
||||
}
|
||||
|
||||
try {
|
||||
if (localUrl) {
|
||||
// Local package.
|
||||
this.iframeSrc = localUrl;
|
||||
} else {
|
||||
this.onlinePlayerUrl = this.onlinePlayerUrl || CoreH5P.instance.h5pPlayer.calculateOnlinePlayerUrl(
|
||||
this.site.getURL(),
|
||||
this.fileUrl || '',
|
||||
this.displayOptions,
|
||||
this.trackComponent,
|
||||
);
|
||||
|
||||
// Never allow downloading in the app. This will only work if the user is allowed to change the params.
|
||||
const src = this.onlinePlayerUrl.replace(
|
||||
CoreH5PCore.DISPLAY_OPTION_DOWNLOAD + '=1',
|
||||
CoreH5PCore.DISPLAY_OPTION_DOWNLOAD + '=0',
|
||||
);
|
||||
|
||||
// Get auto-login URL so the user is automatically authenticated.
|
||||
const url = await this.site.getAutoLoginUrl(src, false);
|
||||
|
||||
// Add the preventredirect param so the user can authenticate.
|
||||
this.iframeSrc = CoreUrlUtils.instance.addParamsToUrl(url, { preventredirect: false });
|
||||
}
|
||||
} catch (error) {
|
||||
CoreDomUtils.instance.showErrorModalDefault(error, 'Error loading H5P package.', true);
|
||||
|
||||
} finally {
|
||||
this.addResizerScript();
|
||||
this.onIframeUrlSet.emit({ src: this.iframeSrc!, online: !!localUrl });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the local URL of the package.
|
||||
*
|
||||
* @return Promise resolved with the local URL.
|
||||
*/
|
||||
protected async getLocalUrl(): Promise<string | undefined> {
|
||||
try {
|
||||
const url = await CoreH5P.instance.h5pPlayer.getContentIndexFileUrl(
|
||||
this.fileUrl!,
|
||||
this.displayOptions,
|
||||
this.trackComponent,
|
||||
this.contextId,
|
||||
this.siteId,
|
||||
);
|
||||
|
||||
return url;
|
||||
} catch (error) {
|
||||
// Index file doesn't exist, probably deleted because a lib was updated. Try to create it again.
|
||||
try {
|
||||
const path = await CoreFilepool.instance.getInternalUrlByUrl(this.siteId, this.fileUrl!);
|
||||
|
||||
const file = await CoreFile.instance.getFile(path);
|
||||
|
||||
await CoreH5PHelper.saveH5P(this.fileUrl!, file, this.siteId);
|
||||
|
||||
// File treated. Try to get the index file URL again.
|
||||
const url = await CoreH5P.instance.h5pPlayer.getContentIndexFileUrl(
|
||||
this.fileUrl!,
|
||||
this.displayOptions,
|
||||
this.trackComponent,
|
||||
this.contextId,
|
||||
this.siteId,
|
||||
);
|
||||
|
||||
return url;
|
||||
} catch (error) {
|
||||
// Still failing. Delete the H5P package?
|
||||
this.logger.error('Error loading downloaded index:', error, this.fileUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the resizer script if it hasn't been added already.
|
||||
*/
|
||||
protected addResizerScript(): void {
|
||||
if (document.head.querySelector('#core-h5p-resizer-script') != null) {
|
||||
// Script already added, don't add it again.
|
||||
return;
|
||||
}
|
||||
|
||||
const script = document.createElement('script');
|
||||
script.id = 'core-h5p-resizer-script';
|
||||
script.type = 'text/javascript';
|
||||
script.src = CoreH5P.instance.h5pPlayer.getResizerScriptUrl();
|
||||
document.head.appendChild(script);
|
||||
}
|
||||
|
||||
/**
|
||||
* H5P iframe has been loaded.
|
||||
*/
|
||||
iframeLoaded(): void {
|
||||
this.onIframeLoaded.emit();
|
||||
this.iframeLoadedOnce = true;
|
||||
|
||||
// Send a resize event to the window so H5P package recalculates the size.
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Component being destroyed.
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.subscription?.unsubscribe();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
<div *ngIf="!showPackage && urlParams" class="core-h5p-placeholder">
|
||||
<ion-button class="core-h5p-placeholder-play-button" fill="clear" (click)="play($event)">
|
||||
<core-icon name="far-play-circle" slot="icon-only"></core-icon>
|
||||
</ion-button>
|
||||
|
||||
<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>
|
||||
|
||||
<core-h5p-iframe *ngIf="showPackage" [fileUrl]="urlParams!.url" [displayOptions]="displayOptions" [onlinePlayerUrl]="src">
|
||||
</core-h5p-iframe>
|
|
@ -0,0 +1,48 @@
|
|||
:host {
|
||||
--core-h5p-placeholder-bg-color: var(--gray);
|
||||
--core-h5p-placeholder-text-color: var(--ion-text-color);
|
||||
|
||||
.core-h5p-placeholder {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 230px;
|
||||
background: url('../../../../../assets/img/icons/h5p.svg') center top 25px / 100px auto no-repeat var(--core-h5p-placeholder-bg-color);
|
||||
color: var(--core-h5p-placeholder-text-color);
|
||||
|
||||
.icon:not([color="success"]) {
|
||||
color: var(--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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
ion-spinner circle {
|
||||
stroke: var(--core-h5p-placeholder-text-color);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,219 @@
|
|||
// (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 { Component, Input, ElementRef, OnInit, OnDestroy, OnChanges, SimpleChange } from '@angular/core';
|
||||
|
||||
import { CoreApp } from '@services/app';
|
||||
import { CoreFilepool } from '@services/filepool';
|
||||
import { CoreSites } from '@services/sites';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { CoreUrlUtils } from '@services/utils/url';
|
||||
import { CorePluginFileDelegate } from '@services/plugin-file-delegate';
|
||||
import { CoreConstants } from '@/core/constants';
|
||||
import { CoreSite } from '@classes/site';
|
||||
import { CoreEvents, CoreEventObserver } from '@singletons/events';
|
||||
import { CoreLogger } from '@singletons/logger';
|
||||
import { CoreH5P } from '@features/h5p/services/h5p';
|
||||
import { CoreH5PDisplayOptions } from '../../classes/core';
|
||||
|
||||
/**
|
||||
* Component to render an H5P package.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'core-h5p-player',
|
||||
templateUrl: 'core-h5p-player.html',
|
||||
styleUrls: ['h5p-player.scss'],
|
||||
})
|
||||
export class CoreH5PPlayerComponent implements OnInit, OnChanges, OnDestroy {
|
||||
|
||||
@Input() src?: string; // The URL of the player to display the H5P package.
|
||||
@Input() component?: string; // Component.
|
||||
@Input() componentId?: string | number; // Component ID to use in conjunction with the component.
|
||||
|
||||
showPackage = false;
|
||||
state?: string;
|
||||
canDownload = false;
|
||||
calculating = true;
|
||||
displayOptions?: CoreH5PDisplayOptions;
|
||||
urlParams?: {[name: string]: string};
|
||||
|
||||
protected site: CoreSite;
|
||||
protected siteId: string;
|
||||
protected siteCanDownload: boolean;
|
||||
protected observer?: CoreEventObserver;
|
||||
protected logger: CoreLogger;
|
||||
|
||||
constructor(
|
||||
public elementRef: ElementRef,
|
||||
) {
|
||||
|
||||
this.logger = CoreLogger.getInstance('CoreH5PPlayerComponent');
|
||||
this.site = CoreSites.instance.getCurrentSite()!;
|
||||
this.siteId = this.site.getId();
|
||||
this.siteCanDownload = this.site.canDownloadFiles() && !CoreH5P.instance.isOfflineDisabledInSite();
|
||||
}
|
||||
|
||||
/**
|
||||
* Component being initialized.
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this.checkCanDownload();
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect changes on input properties.
|
||||
*/
|
||||
ngOnChanges(changes: {[name: string]: SimpleChange}): void {
|
||||
// If it's already playing there's no need to check if it can be downloaded.
|
||||
if (changes.src && !this.showPackage) {
|
||||
this.checkCanDownload();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Play the H5P.
|
||||
*
|
||||
* @param e Event.
|
||||
*/
|
||||
async play(e: MouseEvent): Promise<void> {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
this.displayOptions = CoreH5P.instance.h5pPlayer.getDisplayOptionsFromUrlParams(this.urlParams);
|
||||
this.showPackage = true;
|
||||
|
||||
if (!this.canDownload || (this.state != CoreConstants.OUTDATED && this.state != CoreConstants.NOT_DOWNLOADED)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Download the package in background if the size is low.
|
||||
try {
|
||||
this.attemptDownloadInBg();
|
||||
} catch (error) {
|
||||
this.logger.error('Error downloading H5P in background', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Download the package.
|
||||
*
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
async download(): Promise<void> {
|
||||
if (!CoreApp.instance.isOnline()) {
|
||||
CoreDomUtils.instance.showErrorModal('core.networkerrormsg', true);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Get the file size and ask the user to confirm.
|
||||
const size = await CorePluginFileDelegate.instance.getFileSize({ fileurl: this.urlParams!.url }, this.siteId);
|
||||
|
||||
await CoreDomUtils.instance.confirmDownloadSize({ size: size, total: true });
|
||||
|
||||
// User confirmed, add to the queue.
|
||||
await CoreFilepool.instance.addToQueueByUrl(this.siteId, this.urlParams!.url, this.component, this.componentId);
|
||||
|
||||
} catch (error) {
|
||||
if (CoreDomUtils.instance.isCanceledError(error)) {
|
||||
// User cancelled, stop.
|
||||
return;
|
||||
}
|
||||
|
||||
CoreDomUtils.instance.showErrorModalDefault(error, 'core.errordownloading', true);
|
||||
this.calculateState();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Download the H5P in background if the size is low.
|
||||
*
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
protected async attemptDownloadInBg(): Promise<void> {
|
||||
if (!this.urlParams || !this.src || !this.siteCanDownload || !CoreH5P.instance.canGetTrustedH5PFileInSite() ||
|
||||
!CoreApp.instance.isOnline()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the file size.
|
||||
const size = await CorePluginFileDelegate.instance.getFileSize({ fileurl: this.urlParams.url }, this.siteId);
|
||||
|
||||
if (CoreFilepool.instance.shouldDownload(size)) {
|
||||
// Download the file in background.
|
||||
CoreFilepool.instance.addToQueueByUrl(this.siteId, this.urlParams.url, this.component, this.componentId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the package can be downloaded.
|
||||
*
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
protected async checkCanDownload(): Promise<void> {
|
||||
this.observer && this.observer.off();
|
||||
this.urlParams = CoreUrlUtils.instance.extractUrlParams(this.src || '');
|
||||
|
||||
if (this.src && this.siteCanDownload && CoreH5P.instance.canGetTrustedH5PFileInSite() && this.site.containsUrl(this.src)) {
|
||||
this.calculateState();
|
||||
|
||||
// Listen for changes in the state.
|
||||
try {
|
||||
const eventName = await CoreFilepool.instance.getFileEventNameByUrl(this.siteId, this.urlParams.url);
|
||||
|
||||
this.observer = CoreEvents.on(eventName, () => {
|
||||
this.calculateState();
|
||||
});
|
||||
} catch (error) {
|
||||
// An error probably means the file cannot be downloaded or we cannot check it (offline).
|
||||
}
|
||||
|
||||
} else {
|
||||
this.calculating = false;
|
||||
this.canDownload = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate state of the file.
|
||||
*
|
||||
* @param fileUrl The H5P file URL.
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
protected async calculateState(): Promise<void> {
|
||||
this.calculating = true;
|
||||
|
||||
// Get the status of the file.
|
||||
try {
|
||||
const state = await CoreFilepool.instance.getFileStateByUrl(this.siteId, this.urlParams!.url);
|
||||
|
||||
this.canDownload = true;
|
||||
this.state = state;
|
||||
} catch (error) {
|
||||
this.canDownload = false;
|
||||
} finally {
|
||||
this.calculating = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Component destroyed.
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.observer?.off();
|
||||
}
|
||||
|
||||
}
|
|
@ -16,6 +16,7 @@ import { APP_INITIALIZER, NgModule } from '@angular/core';
|
|||
|
||||
import { CorePluginFileDelegate } from '@services/plugin-file-delegate';
|
||||
import { CORE_SITE_SCHEMAS } from '@services/sites';
|
||||
import { CoreH5PComponentsModule } from './components/components.module';
|
||||
import {
|
||||
CONTENT_TABLE_NAME,
|
||||
LIBRARIES_TABLE_NAME,
|
||||
|
@ -27,6 +28,7 @@ import { CoreH5PPluginFileHandler } from './services/handlers/pluginfile';
|
|||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CoreH5PComponentsModule,
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
|
|
|
@ -55,7 +55,7 @@ export class CoreUrlUtilsProvider {
|
|||
* @param boolToNumber Whether to convert bools to 1 or 0.
|
||||
* @return URL with params.
|
||||
*/
|
||||
addParamsToUrl(url: string, params?: CoreUrlParams, anchor?: string, boolToNumber?: boolean): string {
|
||||
addParamsToUrl(url: string, params?: Record<string, unknown>, anchor?: string, boolToNumber?: boolean): string {
|
||||
let separator = url.indexOf('?') != -1 ? '&' : '?';
|
||||
|
||||
for (const key in params) {
|
||||
|
|
Loading…
Reference in New Issue